std::enable_if
Defined in header <type_traits>
|
||
template
<
bool B, class T =
void
>
struct enable_if; |
(since C++11) | |
If B
is true, std::enable_if
has a public member typedef type
, equal to T
This metafunction is a convenient way to leverage SFINAE prior to C++20's concepts, in particular for conditionally removing functions from the candidate set
std::enable_if
can be used in many forms, including:
- as an additional function argument (not applicable to most operator overloads),
- as a return type (not applicable to constructors and destructors),
- as a class template or function template parameter.
If the program adds specializations for std::enable_if
, the behavior is undefined.
Member types
Type | Definition |
type
|
either T or no such member, depending on the value of B
|
Helper types
template
<
bool B, class T =
void
>
using enable_if_t = typename enable_if<B,T> :: type ; |
(since C++14) | |
Possible implementation
template<bool B, class T = void> struct enable_if {}; template<class T> struct enable_if<true, T> { typedef T type; }; |
Notes
A common mistake is to declare two function templates that differ only in their default template arguments. This does not work because the declarations are treated as redeclarations of the same function template (default template arguments are not accounted for in function template equivalence
/* WRONG */ 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) {} // error: treated as redefinition }; /* RIGHT */ 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 };
Care should be taken when using enable_if
// first translation unit 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 // second translation unit 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
The function templates #1 and #3 have different signatures and are distinct templates. Nonetheless, #2 and #4, despite being instantiations of different function templates, have the same mangled name in the Itanium C++ ABI (_Z4funcI1XLi0EEvv
Example
#include <iostream> #include <new> #include <string> #include <type_traits> namespace detail { void* voidify(const volatile void* ptr) noexcept { return const_cast<void*>(ptr); } } // #1, enabled via the return type template<class T> typename std::enable_if<std::is_trivially_default_constructible<T>::value>::type construct(T*) { std::cout << "default constructing trivially default constructible T\n"; } // same as above template<class T> typename std::enable_if<!std::is_trivially_default_constructible<T>::value>::type construct(T* p) { std::cout << "default constructing non-trivially default constructible T\n"; ::new(detail::voidify(p)) T; } // #2 template<class T, class... Args> std::enable_if_t<std::is_constructible<T, Args&&...>::value> // Using helper type construct(T* p, Args&&... args) { std::cout << "constructing T with operation\n"; ::new(detail::voidify(p)) T(static_cast<Args&&>(args)...); } // #3, enabled via a parameter template<class T> void destroy( T*, typename std::enable_if< std::is_trivially_destructible<T>::value >::type* = 0) { std::cout << "destroying trivially destructible T\n"; } // #4, enabled via a non-type template parameter 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 << "destroying non-trivially destructible T\n"; t->~T(); } // #5, enabled via a type template parameter template<class T, typename = std::enable_if_t<std::is_array<T>::value>> void destroy(T* t) // note: function signature is unmodified { 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) {} // error: has the same signature with #5 */ // the partial specialization of A is enabled via a template parameter template<class T, class Enable = void> class A {}; // primary template template<class T> class A<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {}; // specialization for floating point types 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: matches the primary template A<double>{}; // OK: matches the partial specialization }
Output:
default constructing trivially default constructible T destroying trivially destructible T constructing T with operation destroying non-trivially destructible T
See also
(C++17)
|
void variadic alias template (alias template) |