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
Definido en el encabezado <type_traits>
template < bool B, class T = void >
struct enable_if ;
(desde C++11)

Si B es true , std::enable_if tiene un typedef miembro público type , igual a T ; de lo contrario, no existe el typedef miembro.

Esta metafunción es una forma conveniente de aprovechar SFINAE antes de los concepts de C++20, en particular para eliminar condicionalmente funciones del candidate set basándose en type traits, permitiendo sobrecargas de funciones separadas o especializaciones basadas en esos diferentes type traits.

std::enable_if puede utilizarse de muchas formas, incluyendo:

  • como un argumento de función adicional (no aplicable a la mayoría de las sobrecargas de operadores),
  • como un tipo de retorno (no aplicable a constructores y destructores),
  • como un parámetro de plantilla de clase o plantilla de función.

Si el programa añade especializaciones para std::enable_if , el comportamiento no está definido.

Contenidos

Tipos de miembros

Tipo Definición
type ya sea T o ningún miembro de este tipo, dependiendo del valor de B

Tipos auxiliares

template < bool B, class T = void >
using enable_if_t = typename enable_if < B,T > :: type ;
(desde C++14)

Implementación posible

template<bool B, class T = void>
struct enable_if {};
template<class T>
struct enable_if<true, T> { typedef T type; };

Notas

Un error común es declarar dos plantillas de función que solo difieren en sus argumentos de plantilla predeterminados. Esto no funciona porque las declaraciones se tratan como redeclaraciones de la misma plantilla de función (los argumentos de plantilla predeterminados no se tienen en cuenta en equivalencia de plantillas de función ).

/* INCORRECTO */
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: tratado como redefinición
};
/* CORRECTO */
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
};

Se debe tener cuidado al usar enable_if en el tipo de un parámetro de plantilla constante de una plantilla de función en el ámbito del espacio de nombres. Algunas especificaciones ABI como la ABI de Itanium no incluyen las partes dependientes de la instanciación de los parámetros de plantilla constantes en el proceso de name mangling, lo que significa que especializaciones de dos plantillas de función distintas podrían terminar con el mismo nombre alterado y ser enlazadas erróneamente. Por ejemplo:

// primera unidad de traducción
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
// segunda unidad de traducción
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

Las plantillas de función #1 y #3 tienen firmas diferentes y son plantillas distintas. Sin embargo, #2 y #4, a pesar de ser instanciaciones de diferentes plantillas de función, tienen el mismo nombre ofuscado en el Itanium C++ ABI ( _Z4funcI1XLi0EEvv ), lo que significa que el enlazador considerará erróneamente que son la misma entidad.

Ejemplo

#include <iostream>
#include <new>
#include <string>
#include <type_traits>
namespace detail
{ 
    void* voidify(const volatile void* ptr) noexcept { return const_cast<void*>(ptr); } 
}
// #1, habilitado mediante el tipo de retorno
template<class T>
typename std::enable_if<std::is_trivially_default_constructible<T>::value>::type 
    construct(T*) 
{
    std::cout << "construcción por defecto de T trivialmente construible por defecto\n";
}
// igual que arriba
template<class T>
typename std::enable_if<!std::is_trivially_default_constructible<T>::valor>::type 
    construct(T* p) 
{
    std::cout << "construcción por defecto de T no trivialmente construible por defecto\n";
    ::new(detail::voidify(p)) T;
}
// #2
template<class T, class... Args>
std::enable_if_t<std::is_constructible<T, Args&&...>::value> // Usando tipo auxiliar
    construct(T* p, Args&&... args) 
{
    std::cout << "construyendo T con operación\n";
    ::new(detail::voidify(p)) T(static_cast<Args&&>(args)...);
}
// #3, habilitado mediante un parámetro
template<class T>
void destroy(
    T*, 
    typename std::enable_if<
        std::is_trivially_destructible<T>::valor
    >::type* = 0)
{
    std::cout << "destruyendo T trivialmente destructible\n";
}
// #4, habilitado mediante un parámetro de plantilla constante
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 << "destruyendo T no trivialmente destructible\n";
    t->~T();
}
// #5, habilitado mediante un parámetro de plantilla de tipo
template<class T,
	 typename = std::enable_if_t<std::is_array<T>::valor>>
void destroy(T* t) // nota: la firma de la función no está modificada
{
    for (std::size_t i = 0; i < std::extent<T>::valor; ++i)
        destroy((*t)[i]);
}
/*
template<class T,
	 typename = std::enable_if_t<std::is_void<T>::value>>
void destroy(T* t) {} // error: tiene la misma firma que #5
*/
// la especialización parcial de A está habilitada mediante un parámetro de plantilla
template<class T, class Enable = void>
class A {}; // plantilla principal
template<class T>
class A<T, typename std::enable_if<std::is_floating_point<T>::valor>::type>
{}; // especialización para tipos de punto flotante
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), "Hola");
    destroy(reinterpret_cast<std::string*>(&u));
    A<int>{}; // OK: coincide con la plantilla principal
    A<double>{}; // OK: coincide con la especialización parcial
}

Salida:

construcción por defecto de T trivialmente construible por defecto
destrucción de T trivialmente destructible
construcción de T con operación
destrucción de T no trivialmente destructible

Véase también

(C++17)
plantilla de alias variádica void
(plantilla de alias)