Класс 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> содержит вложенное определение типа "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 также подходит для обычных конструкторов. Но он не работает с операторами преобразования, так как они не могут принимать дополнительные параметры. Он также не подходит для конструкторов с переменным числом аргументов, так как из-за добавления параметров пакет параметров функции становится невыводимым контекстом, что противоречит цели enable_if.
В сценарии 3 используется имя Dummy, но это необязательно. Подойдет и просто "typename = typename", но если вы считаете, что это выглядит странно, можно использовать имя-заглушку (только не применяйте имя, которое может использоваться в определении функции). Если не передать тип функции enable_if, по умолчанию используется тип void, и это вполне логично, так как Dummy не играет никакой роли. Такой метод подходит для всего, в том числе для операторов преобразования и конструкторов с переменным числом аргументов.
Сценарий 4 работает для конструкторов без возвращаемых типов, что устраняет ограничение упаковки сценария 1. Однако сценарий 4 применяется только для аргументов функции без шаблонов, которые не всегда доступны. (При использовании сценария 4 для аргументов функции на основе шаблона устранение аргументов шаблона не работает.)
enable_if — это мощное средство, которое может быть опасным при неправильном использовании. Так как цель функции — удалить кандидаты до разрешения перегрузки, при ее неправильном применении результаты могут быть очень запутанными. Вот несколько рекомендаций.
Не используйте enable_if для выбора между реализациями во время компиляции. Не пишите одну функцию enable_if для CONDITION и другую для !CONDITION. Используйте шаблон отправки тегов, например, алгоритм может выбирать реализации в зависимости от силы указанных итераторов.
Не используйте enable_if для применения требований. Если вы хотите проверить параметры шаблонов и проверка завершается неудачно и вызывает ошибку вместо выбора другой реализации, используйте static_assert.
Используйте enable_if при наличии набора перегрузок, который делает код неоднозначным. Чаще всего это происходит в конструкторах с неявным преобразованием.
Пример
В этом примере описывается, как функция-шаблон STL std::make_pair() использует 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