std:: enable_if
|
Definiert im Header
<type_traits>
|
||
|
template
<
bool
B,
class
T
=
void
>
struct enable_if ; |
(seit C++11) | |
Wenn
B
true
ist, besitzt
std::enable_if
eine öffentliche Member-Typdefinition
type
, gleich
T
; andernfalls existiert keine Member-Typdefinition.
Diese Metafunktion ist eine praktische Möglichkeit, SFINAE vor C++20s Concepts zu nutzen, insbesondere um Funktionen bedingt aus dem Kandidatensatz zu entfernen basierend auf Type Traits, was separate Funktionsüberladungen oder Spezialisierungen basierend auf diesen verschiedenen Type Traits ermöglicht.
std::enable_if
kann in vielen Formen verwendet werden, einschließlich:
- als zusätzliches Funktionsargument (nicht anwendbar auf die meisten Operatorüberladungen),
- als Rückgabetyp (nicht anwendbar auf Konstruktoren und Destruktoren),
- als Klassen-Template- oder Funktions-Template-Parameter.
Wenn das Programm Spezialisierungen für
std::enable_if
hinzufügt, ist das Verhalten undefiniert.
Inhaltsverzeichnis |
Mitgliedertypen
| Typ | Definition |
type
|
entweder
T
oder kein solches Mitglied, abhängig vom Wert von
B
|
Hilfstypen
|
template
<
bool
B,
class
T
=
void
>
using enable_if_t = typename enable_if < B,T > :: type ; |
(seit C++14) | |
Mögliche Implementierung
template<bool B, class T = void> struct enable_if {}; template<class T> struct enable_if<true, T> { typedef T type; }; |
Hinweise
Ein häufiger Fehler ist die Deklaration zweier Funktions-Templates, die sich nur in ihren Standard-Template-Argumenten unterscheiden. Dies funktioniert nicht, da die Deklarationen als Neudeklarationen desselben Funktions-Templates behandelt werden (Standard-Template-Argumente werden nicht berücksichtigt bei der Funktions-Template-Äquivalenz ).
/* FALSCH */ 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) {} // Fehler: wird als Neudefinition behandelt }; /* KORREKT */ 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 };
Bei der Verwendung von
enable_if
im Typ eines konstanten Template-Parameters einer Namensbereichs-Funktionsvorlage ist Vorsicht geboten. Einige ABI-Spezifikationen wie die Itanium ABI schließen die instantiierungsabhängigen Teile konstanter Template-Parameter nicht in das Name Mangling ein, was bedeutet, dass Spezialisierungen von zwei verschiedenen Funktionsvorlagen möglicherweise denselben gemangelten Namen erhalten und fälschlicherweise miteinander verlinkt werden. Beispiel:
// erste Übersetzungseinheit 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 // zweite Übersetzungseinheit 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
Die Funktions-Templates #1 und #3 haben unterschiedliche Signaturen und sind verschiedene Templates. Nichtsdestotrotz haben #2 und #4, obwohl sie Instanziierungen verschiedener Funktions-Templates sind, den gleichen gemangelten Namen
im Itanium C++ ABI
(
_Z4funcI1XLi0EEvv
), was bedeutet, dass der Linker sie fälschlicherweise als dieselbe Entität betrachten wird.
Beispiel
#include <iostream> #include <new> #include <string> #include <type_traits> namespace detail { void* voidify(const volatile void* ptr) noexcept { return const_cast<void*>(ptr); } } // #1, aktiviert über den Rückgabetyp 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"; } // wie oben 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> // Verwendung eines Hilfstyps construct(T* p, Args&&... args) { std::cout << "Konstruktion von T mit Operation\n"; ::new(detail::voidify(p)) T(static_cast<Args&&>(args)...); } // #3, aktiviert über einen 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, aktiviert über einen konstanten 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 << "Zerstören von nicht-trivial zerstörbarem T\n"; t->~T(); } // #5, aktiviert über einen Typ-Template-Parameter template<class T, typename = std::enable_if_t<std::is_array<T>::value>> void destroy(T* t) // Hinweis: Funktionssignatur ist unverändert { 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) {} // Fehler: hat dieselbe Signatur wie #5 */ // die partielle Spezialisierung von A wird über einen Template-Parameter aktiviert 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> {}; // Spezialisierung für Gleitkommatypen 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), "Hallo"); destroy(reinterpret_cast<std::string*>(&u)); A<int>{}; // OK: entspricht der primären Vorlage A<double>{}; // OK: entspricht der partiellen Spezialisierung }
Ausgabe:
default constructing trivially default constructible T destroying trivially destructible T constructing T with operation destroying non-trivially destructible T
Siehe auch
|
(C++17)
|
void variadische Alias-Vorlage
(Alias-Vorlage) |