Класс enable_if
Условно создает экземпляр типа для разрешения перегрузки SFINAE. Вложенное определение типа enable_if<Condition,Type>::type
(синоним для Type
) существует, если и только если значение Condition
равно true
.
template <bool B, class T = void>
struct enable_if;
B
Значение, определяющее наличие результирующего типа.
T
Тип для создания экземпляра, если B
есть true
.
Если B
имеет true
enable_if<B, T>
вложенный типdef с именем typedef с именем type, который является синонимомT
.
Если B
это false
так, enable_if<B, T>
не имеет вложенного типа с именем "type".
Предоставляется следующий шаблон псевдонима.
template <bool B, class T = void>
using enable_if_t = typename enable_if<B,T>::type;
В C++сбой подстановки параметров шаблона не является ошибкой в самом деле— это называется SFINAE (сбой подстановки не является ошибкой). Обычно enable_if
используется для удаления кандидатов из разрешения перегрузки, т. е. функция отбраковывает набор перегрузки, чтобы одно определение было отброшено в пользу другого. Это соответствует поведению SFINAE. Дополнительные сведения о SFINAE см. в разделе "Сбой подстановки" не является ошибкой в Википедии.
Вот 4 примера сценариев.
- Сценарий 1. Упаковка возвращаемого типа функции:
template <your_stuff>
typename enable_if<your_condition, your_return_type>::type
yourfunction(args) {// ...
}
// The alias template makes it more concise:
template <your_stuff>
enable_if_t<your_condition, your_return_type>
yourfunction(args) {// ...
}
- Сценарий 2. Добавление параметра функции с аргументом по умолчанию:
template <your_stuff>
your_return_type_if_present
yourfunction(args, enable_if_t<your condition, FOO> = BAR) {// ...
}
- Сценарий 3. Добавление параметра шаблона с аргументом по умолчанию:
template <your_stuff, typename Dummy = enable_if_t<your_condition>>
rest_of_function_declaration_goes_here
- Сценарий 4. Если функция содержит аргумент без шаблона, ее тип можно упаковать:
template <typename T>
void your_function(const T& t,
enable_if_t<is_something<T>::value, const string&>
s) {// ...
}
Сценарий 1 не применяется к конструкторам и операторам преобразования, так как у них нет возвращаемых типов.
В сценарии 2 параметр остается без имени. Можно использовать ::type Dummy = BAR
, но имя Dummy
не играет роли, поэтому указание имени, вероятно, вызовет предупреждение о параметре без ссылки. Необходимо выбрать тип параметра функции FOO
и аргумент по умолчанию BAR
. Можно использовать int
и 0
, но тогда пользователи кода смогут случайно передать функции дополнительное целое число, которое будет проигнорировано. Мы рекомендуем использовать void **
и значение 0
или nullptr
, так как почти ничего нельзя преобразовать в тип void **
.
template <your_stuff>
your_return_type_if_present
yourfunction(args, typename enable_if<your_condition, void **>::type = nullptr) {// ...
}
Сценарий 2 также подходит для обычных конструкторов. Но он не работает с операторами преобразования, так как они не могут принимать дополнительные параметры. Он также не работает для variadic
конструкторов, так как добавление дополнительных параметров делает пакет параметров функции неисключаемой контекстом и тем самым побеждает назначение enable_if
.
В сценарии 3 используется имя Dummy
, но это необязательно. Подойдет и просто "typename = typename
", но, если вы считаете, что это выглядит странно, можно использовать имя-заглушку (только не применяйте имя, которое может использоваться в определении функции). Если не передать тип функции enable_if
, по умолчанию используется тип void, и это вполне логично, так как Dummy
не играет никакой роли. Это работает для всего, включая операторы преобразования и variadic
конструкторы.
Сценарий 4 работает для конструкторов без возвращаемых типов, что устраняет ограничение упаковки сценария 1. Однако сценарий 4 применяется только для аргументов функции без шаблонов, которые не всегда доступны. (При использовании сценария 4 для аргументов функции на основе шаблона устранение аргументов шаблона не работает.)
enable_if
— это мощное средство, которое может быть опасным при неправильном использовании. Так как цель функции — удалить кандидаты до разрешения перегрузки, при ее неправильном применении результаты могут быть очень запутанными. Вот несколько рекомендаций.
Не используйте
enable_if
выбор между реализациями во время компиляции. Не пишите одну функциюenable_if
дляCONDITION
и другую для!CONDITION
. Используйте шаблон отправки тегов, например, алгоритм может выбирать реализации в зависимости от силы указанных итераторов.Не используйте
enable_if
для принудительного применения требований. Если вы хотите проверить параметры шаблона, а если проверка завершается ошибкой, вместо выбора другой реализации используйтеstatic_assert
.Используйте
enable_if
при наличии набора перегрузок, который делает код неоднозначным. Чаще всего это происходит в конструкторах с неявным преобразованием.
В этом примере объясняется, как функция std::make_pair()
шаблона стандартной библиотеки C++ использует преимущества enable_if
.
void func(const pair<int, int>&);
void func(const pair<string, string>&);
func(make_pair("foo", "bar"));
В этом примере make_pair("foo", "bar")
возвращает pair<const char *, const char *>
. Разрешение перегрузок должно определить требуемую функцию func()
. pair<A, B>
содержит конструктор с неявным преобразованием из pair<X, Y>
. Это не новый элемент, он был представлен в C++98. Однако в C++98/03 сигнатура конструктора с неявным преобразованием всегда существует, даже если это pair<int, int>(const pair<const char *, const char *>&)
. Процессу разрешения перегрузок все равно, что попытка создать экземпляр конструктора приведет к отрицательным последствиям, так как const char *
нельзя неявно преобразовать в int
. Процесс смотрит только на сигнатуры перед созданием экземпляра определений функций. Поэтому этот пример кода неоднозначен, так как существуют сигнатуры для преобразования pair<const char *, const char *>
в pair<int, int>
и pair<string, string>
.
В C++11 эта неоднозначность была устранена с помощью enable_if
, чтобы конструкция pair<A, B>(const pair<X, Y>&)
существовала только, если const X&
неявно преобразуется в A
, а const Y&
неявно преобразуется в B
. Это позволяет разрешать перегрузку, чтобы определить, pair<const char *, const char *>
что не преобразуется в pair<int, int>
и что перегрузка, которая принимается pair<string, string>
, является жизнеспособной.
Заголовок: <type_traits>
Пространство имен: std