std::enable_if
Определено в заголовочном файле <type_traits>
|
||
template< bool B, class T = void > struct enable_if; |
(начиная с C++11) | |
Если B
является true, std::enable_if
имеет открытый typedef элемент type
, равный T
, иначе typedef элемент не определён.
Эта метафункция представляет собой удобный способ использовать SFINAE до концептов C++20, в частности, для условного удаления функций из набора кандидатов на основе свойств типа, что позволяет использовать отдельные перегрузки функций или специализации на основе различных свойств типа.
std::enable_if
можно использовать во многих формах, в том числе:
- в качестве дополнительного аргумента функции (не применимо к перегрузкам операторов)
- как возвращаемый тип (не применимо к конструкторам и деструкторам)
- как шаблон класса или параметр шаблона функции
Поведение программы, добавляющей специализации для std::enable_if
не определено.
Содержание |
[править] Типы-элементы
Тип | Определение |
type
|
либо T , либо нет такого элемента, в зависимости от значения B
|
[править] Вспомогательные типы
template< bool B, class T = void > using enable_if_t = typename enable_if<B,T>::type; |
(начиная с C++14) | |
[править] Возможная реализация
template<bool B, class T = void> struct enable_if {}; template<class T> struct enable_if<true, T> { typedef T type; }; |
[править] Примечание
Распространённой ошибкой является объявление двух шаблонов функций, которые отличаются только аргументами шаблона по умолчанию. Это не работает, потому что объявления рассматриваются как повторные объявления одного и того же шаблона функции (аргументы шаблона по умолчанию не учитываются при эквивалентности шаблона функции).
/* НЕПРАВИЛЬНО */ struct T { enum { int_t, float_t } type; template <typename Integer, typename = std::enable_if_t<std::is_integral<Integer>::value> > T(Integer) : type(int_t) {} template <typename Floating, typename = std::enable_if_t<std::is_floating_point<Floating>::value> > T(Floating) : type(float_t) {} // ошибка: рассматривается как переопределение }; /* ВЕРНО */ struct T { enum { int_t, float_t } type; template <typename Integer, std::enable_if_t<std::is_integral<Integer>::value, bool> = true > T(Integer) : type(int_t) {} template <typename Floating, std::enable_if_t<std::is_floating_point<Floating>::value, bool> = true > T(Floating) : type(float_t) {} // OK };
Следует соблюдать осторожность при использовании enable_if
в типе параметра шаблона не типа шаблона функции пространства имён. Некоторые спецификации ABI, такие как Itanium ABI, не включают зависящие от инстанцирования части параметров шаблона не типов при изменении, а это означает, что специализации двух разных шаблонов функций могут иметь одно и то же искажённое имя и быть ошибочно связанными друг с другом. Например:
// первая единица трансляции struct X { enum { value1 = true, value2 = true }; }; template<class T, std::enable_if_t<T::value1, int> = 0> void func() {} // #1 template void func<X>(); // #2 // вторая единица трансляции struct X { enum { value1 = true, value2 = true }; }; template<class T, std::enable_if_t<T::value2, int> = 0> void func() {} // #3 template void func<X>(); //#4
Шаблоны функций #1 и #3 имеют разные сигнатуры и являются разными шаблонами. Тем не менее, #2 и #4, несмотря на то, что они являются экземплярами разных шаблонов функций, имеют одно и то же искажённое имя в Itanium C++ ABI (_Z4funcI1XLi0EEvv
), что означает, что компоновщик ошибочно посчитает их одной и той же сущностью.
[править] Пример
#include <type_traits> #include <new> #include <iostream> #include <string> namespace detail { void* voidify(const volatile void* ptr) noexcept { return const_cast<void*>(ptr); } } // #1, включается через возвращаемый тип template<class T> typename std::enable_if<std::is_trivially_default_constructible<T>::value>::type construct(T*) { std::cout << "создание по умолчанию тривиально конструируемого по умолчанию T\n"; } // то же, что и выше template<class T> typename std::enable_if<!std::is_trivially_default_constructible<T>::value>::type construct(T* p) { std::cout << "создание по умолчанию нетривиального конструируемого по умолчанию T\n"; ::new(detail::voidify(p)) T; } // #2 template<class T, class... Args> // Использование вспомогательного типа std::enable_if_t<std::is_constructible<T, Args&&...>::value> construct(T* p, Args&&... args) { std::cout << "создание T с операцией\n"; ::new(detail::voidify(p)) T(static_cast<Args&&>(args)...); } // #3, включается через параметр template<class T> void destroy( T*, typename std::enable_if< std::is_trivially_destructible<T>::value >::type* = 0 ){ std::cout << "уничтожение тривиально разрушаемого T\n"; } // #4, включено с помощью параметра шаблона, отличного от типа template<class T, typename std::enable_if< !std::is_trivially_destructible<T>{} && (std::is_class<T>{} || std::is_union<T>{}), bool>::type = true> void destroy(T* t) { std::cout << "уничтожение нетривиально разрушаемого T\n"; t->~T(); } // #5, включается через параметр шаблона типа template<class T, typename = std::enable_if_t<std::is_array<T>::value> > void destroy(T* t) // примечание: сигнатура функции не изменена { for(std::size_t i = 0; i < std::extent<T>::value; ++i) { destroy((*t)[i]); } } /* template<class T, typename = std::enable_if_t<std::is_void<T>::value> > void destroy(T* t){} // ошибка: имеет ту же сигнатуру, что и #5 */ // частичная специализация A включается через параметр шаблона template<class T, class Enable = void> class A {}; // основной шаблон template<class T> class A<T, typename std::enable_if<std::is_floating_point<T>::value>::type> { }; // специализация для типов с плавающей запятой int main() { union { int i; char s[sizeof(std::string)]; } u; construct(reinterpret_cast<int*>(&u)); destroy(reinterpret_cast<int*>(&u)); construct(reinterpret_cast<std::string*>(&u),"Hello"); destroy(reinterpret_cast<std::string*>(&u)); A<int>{}; // OK: соответствует основному шаблону A<double>{}; // OK: соответствует частичной специализации }
Вывод:
создание по умолчанию тривиально конструируемого по умолчанию T уничтожение тривиально разрушаемого T создание T с операцией уничтожение нетривиально разрушаемого T
[править] Смотрите также
(C++17) |
псевдоним шаблона с переменным числом аргументов типа void (псевдоним шаблона) |