Spec-Zone.ru › C++

std::enable_if

Определён в заголовочном файле <type_traits>
template< bool B, class T = void >
struct enable_if;
(начиная с C++11)

Если B — true, std::enable_if имеет открытый псевдоним типа члена type, равный T; в противном случае такого псевдонима типа члена нет.

Эта метафункция позволяет удобно использовать 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; };

Примечания

Распространённая ошибка — объявлять два шаблона функций, отличающиеся только аргументами шаблона по умолчанию. Это не работает, поскольку такие объявления рассматриваются как повторные объявления одного и того же шаблона функции (аргументы шаблона по умолчанию не учитываются при эквивалентности шаблонов функций).

/* WRONG */

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: treated as redefinition
};

/* RIGHT */

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, не включают зависящие от инстанцирования части нетиповых параметров шаблона в манглированное имя. В результате специализации двух различных шаблонов функций могут получить одинаковое манглированное имя и быть ошибочно связаны друг с другом. Например:

// first translation unit

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

// second translation unit

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, enabled via the return type
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";
}

// same as above
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> // Using helper type
    construct(T* p, Args&&... args) 
{
    std::cout << "constructing T with operation\n";
    ::new(detail::voidify(p)) T(static_cast<Args&&>(args)...);
}

// #3, enabled via a 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, enabled via a non-type 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 << "destroying non-trivially destructible T\n";
    t->~T();
}

// #5, enabled via a type template parameter
template<class T,
         typename = std::enable_if_t<std::is_array<T>::value>>
void destroy(T* t) // note: function signature is unmodified
{
    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) {} // error: has the same signature with #5
*/

// the partial specialization of A is enabled via a template parameter
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>
{}; // specialization for floating point types

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: matches the primary template
    A<double>{}; // OK: matches the partial specialization
}

Вывод:

default constructing trivially default constructible T
destroying trivially destructible T
constructing T with operation
destroying non-trivially destructible T

См. также

void_t
(C++17)
вариадический шаблон псевдонима void
(шаблон псевдонима)
  • static_assert
  • SFINAE
  • Ограничения и концепты

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/types/enable_if

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API