#опытным
Не так давно мы разбирали функцию std::clamp, которая ограничивает значение переменной верхней и нижней границей:
double increment_speed(double curr_speed, double acceleration, double time_delta) {
curr_speed += acceleration * time_delta;
return std::clamp(curr_speed, kMinSpeed, kMaxSpeed);
}В таком виде это прекрасно работает. Однако у std::clamp есть одна проблема: все три ее параметра должны быть одного типа:
template<class T>
constexpr const T& clamp( const T& v, const T& lo, const T& hi );
Если попытаться использовать функцию с разными типами, то получим ошибку вывода типов:
auto bounded = std::clamp(42, 3.14, 69.f); // ERROR!
Компилятор не поймет, какой тип Т имелся ввиду, потому что все три аргумента разных типов.
Можно было сделать 3 отдельных параметра:
template<class T1, class T2, class T3>
constexpr const T& clamp( const T1& v, const T2& lo, const T3& hi );
Но тогда приходилось бы навешивать какие-то compile-time проверки совместимости типов.
Есть подход получше - использовать C++20 std::type_identity. Это максимально простая обертка над типом:
template<class T>
struct type_identity { using type = T; };
template< class T >
using type_identity_t = type_identity<T>::type;
Но этот простой финт ушами дает очень важный эффект - отсутствие контекста вывода в шаблонах:
template <class T>
auto bound(T num, typename std::type_identity<T>::type low, typename std::type_identity<T>::type high) {
return std::clamp(num, low, high);
}
auto bounded = bound(25.5, 20, 25);
При использовании зависимых имен(type - зависимое имя шаблонного класса type_identity) компилятор не вывод тип Т для аргументов. Он либо полагается на явное указание аргументов при инстанциации, либо на вывод типа из других параметров. В последнем сниппете только параметр num находится в контексте вывода и по нему компилятор выводит тип Т. Типы параметров low и high не зависят от того, какие соответствующие аргументы мы передаем при вызове функции. Они определяются выведенным типом первого аргумента.
В данном случае тип num выведется в double, поэтому и типы low и high тоже будут double. При вызове просто сработает неявное преобразование от int к double.
Также type_identity можно использовать для того, чтобы запретить вывод типов и заставить пользователя явно прописывать шаблонные параметры. Это может быть важно для точной передачи типа:
template<class T>
void foo(typename std::type_identity<T>::type arg) {}
foo<int>(42); // T жёстко задаётся как int
// foo(42); // Ошибка: вывод T невозможен!
Тоже самое для вариадиков:
template <typename... Ts>
void process(typename std::type_identity<std::tuple<Ts...>>::type data) {
}
process<int, double>(std::tuple{1, 2.0}); // OK
process(std::tuple{1, 2.0}); // ERROR, не указаны типы шаблонных параметров
Прикольный инструмент для тонкой настройки вашего шаблонного кода.
Спасибо @d7d1cd за идею для поста)
Turn off deduction when it is not needed. Stay cool.
#template #cpp20