Namespaces
Variants

std:: enable_if

From cppreference.net
Metaprogramming library
Type traits
Type categories
(C++11)
(C++11) ( DR* )
Type properties
(C++11)
(C++11)
(C++14)
(C++11) (deprecated in C++26)
(C++11) ( until C++20* )
(C++11) (deprecated in C++20)
(C++11)
Type trait constants
Metafunctions
(C++17)
Supported operations
Relationships and property queries
Type modifications
Type transformations
(C++11) (deprecated in C++23)
(C++11) (deprecated in C++23)
(C++11)
(C++11) ( until C++20* ) (C++17)

enable_if
(C++11)
(C++17)
Compile-time rational arithmetic
Compile-time integer sequences
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)