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
Определено в заголовочном файле <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 <iostream>
#include <new>
#include <string>
#include <type_traits>
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>::значение>::type 
    construct(T*) 
{
    std::cout << "конструирование по умолчанию тривиально конструируемого по умолчанию T\n";
}
// то же самое
template<class T>
typename std::enable_if<!std::is_trivially_default_constructible<T>::значение>::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&&...>::значение> // Использование вспомогательного типа
    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>::значение
    >::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>::значение>>
void destroy(T* t) // примечание: сигнатура функции не изменена
{
    for (std::size_t i = 0; i < std::extent<T>::значение; ++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>::значение>::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), "Привет");
    destroy(reinterpret_cast<std::string*>(&u));
    A<int>{}; // OK: соответствует основному шаблону
    A<double>{}; // OK: соответствует частичной специализации
}

Вывод:

конструирование по умолчанию тривиально конструируемого по умолчанию T
уничтожение тривиально уничтожаемого T
конструирование T с операцией
уничтожение нетривиально уничтожаемого T

Смотрите также

(C++17)
void вариативный псевдоним шаблона
(псевдоним шаблона)