Пространства имён
Варианты
Действия

std::enable_if

Материал из cppreference.com
< cpp‎ | types
 
 
Библиотека метапрограммирования
Свойства типов
Категории типов
(C++11)
(C++14)  
(C++11)
(C++11)
(C++11)
(C++11)
(C++11)
(C++11)
(C++11)
Свойства типов
(C++11)
(C++11)
(C++14)
(C++11)
(C++11)(до C++20*)
(C++11)(устарело в C++20)
(C++11)
Константы свойств типа
Метафункции
(C++17)
Поддерживаемые операции
Запросы отношений и свойств
Модификации типов
(C++11)(C++11)(C++11)
Преобразования типов
(C++11)(устарело в C++23)
(C++11)(устарело в C++23)
(C++11)
enable_if
(C++11)
(C++17)

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

Вывод:

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

[править] Смотрите также

(C++17)
псевдоним шаблона с переменным числом аргументов типа void
(псевдоним шаблона) [править]