Скотт Мейерс опубликовал содержание и статус его следующей книги EC ++ 11.
Он написал, что один пункт в книге может быть «Избегайте std::enable_if
в функции подписи «.
std::enable_if
может использоваться в качестве аргумента функции, в качестве возвращаемого типа или в качестве шаблона класса или параметра шаблона функции для условного удаления функций или классов из разрешения перегрузки.
В этот вопрос все три решения показаны.
В качестве параметра функции:
template<typename T>
struct Check1
{
template<typename U = T>
U read(typename std::enable_if<
std::is_same<U, int>::value >::type* = 0) { return 42; }
template<typename U = T>
U read(typename std::enable_if<
std::is_same<U, double>::value >::type* = 0) { return 3.14; }
};
В качестве параметра шаблона:
template<typename T>
struct Check2
{
template<typename U = T, typename std::enable_if<
std::is_same<U, int>::value, int>::type = 0>
U read() { return 42; }
template<typename U = T, typename std::enable_if<
std::is_same<U, double>::value, int>::type = 0>
U read() { return 3.14; }
};
Как тип возврата:
template<typename T>
struct Check3
{
template<typename U = T>
typename std::enable_if<std::is_same<U, int>::value, U>::type read() {
return 42;
}
template<typename U = T>
typename std::enable_if<std::is_same<U, double>::value, U>::type read() {
return 3.14;
}
};
std::enable_if
в функции подписи « касается использования в качестве возвращаемого типа (который не является частью обычной сигнатуры функции, но специализации шаблона)?Поместите взлом в параметры шаблона.
enable_if
Подход к параметру шаблона имеет как минимум два преимущества перед остальными:
читабельность: использование enable_if и типы return / аргумент не объединяются в один беспорядочный кусок двусмысленных типов и обращений к вложенным типам; даже несмотря на то, что беспорядок неоднозначности и вложенного типа может быть уменьшен с помощью шаблонов псевдонимов, это все равно объединит две несвязанные вещи. Использование enable_if связано с параметрами шаблона, а не с типами возврата. Наличие их в параметрах шаблона означает, что они ближе к тому, что имеет значение;
универсальная применимостьконструкторы не имеют возвращаемых типов, а некоторые операторы не могут иметь дополнительных аргументов, поэтому ни один из двух других параметров не может быть применен везде. Помещение enable_if в параметр шаблона работает везде, так как в любом случае вы можете использовать только SFINAE для шаблонов.
Для меня аспект читабельности является большим мотивирующим фактором в этом выборе.
std::enable_if
опирается наСбой Substition не является ошибкой«(иначе СФИНА) принцип во время вычет аргумента шаблона. Это очень хрупкий особенность языка, и вы должны быть очень осторожны, чтобы сделать это правильно.
enable_if
содержит вложенный шаблон или определение типа (подсказка: ищите ::
токены), то разрешение этих вложенных храмов или типов обычно не выводимый контекст. Любой сбой замещения в таком невыбранном контексте является ошибка.enable_if
перегрузки не могут иметь никакого перекрытия, потому что разрешение перегрузки будет неоднозначным. Это то, что вы, как автор, должны проверить сами, хотя вы получите хорошие предупреждения компилятора.enable_if
манипулирует набором жизнеспособных функций во время разрешения перегрузки, которые могут иметь неожиданные взаимодействия в зависимости от присутствия других функций, которые вводятся из других областей (например, через ADL). Это делает его не очень надежным.Короче говоря, когда это работает, это работает, но когда это не так, это может быть очень трудно для отладки. Очень хорошей альтернативой является использование диспетчеризация тегов, то есть делегировать функции реализации (обычно в detail
пространства имен или в вспомогательном классе), который получает фиктивный аргумент на основе того же условия времени компиляции, которое вы используете в enable_if
,
template<typename T>
T fun(T arg)
{
return detail::fun(arg, typename some_template_trait<T>::type() );
}
namespace detail {
template<typename T>
fun(T arg, std::false_type /* dummy */) { }
template<typename T>
fun(T arg, std::true_type /* dummy */) {}
}
Диспетчеризация тегов не манипулирует набором перегрузки, но помогает вам выбрать именно ту функцию, которую вы хотите, предоставляя правильные аргументы через выражение времени компиляции (например, в признаке типа). По моему опыту, это намного легче отладить и получить права. Если вы начинающий писатель библиотеки со сложными чертами типа, вам может понадобиться enable_if
как-то, но для большинства регулярных условий компиляции это не рекомендуется.
Какое решение должно быть предпочтительным и почему я должен избегать других?
Параметр шаблона
Это может легко использоваться неправильно и приводит к ошибкам с перегрузками:
template<typename T, typename = std::enable_if_t<std::is_same<T, int>::value>>
void f() {/*...*/}
template<typename T, typename = std::enable_if_t<std::is_same<T, float>::value>>
void f() {/*...*/} // Redefinition: both are just template<typename, typename> f()
уведомление typename = std::enable_if_t<cond>
вместо правильного std::enable_if_t<cond, int>::type = 0
тип возврата:
Последнее, в параметре функции:
+
, -
, *
…)void* = nullptr
) (поэтому указатель на функцию будет отличаться и т. д.)Существуют ли различия для шаблонов функций-членов и не-членов?
Есть тонкие различия с наследованием и using
:
Согласно using-declarator
(выделение мое):
Набор объявлений, введенных декларатором using, определяется путем выполнения поиска подходящего имени ([basic.lookup.qual], [class.member.lookup]) для имени в деклараторе using, за исключением функций, которые скрыты, как описано ниже.
…
Когда использование-декларатор переносит объявления из базового класса в производный класс, функции-члены и шаблоны функций-членов в производном классе переопределяют и / или скрывают функции-члены и шаблоны функций-членов с тем же именем, параметром-тип-списком, cv-квалификацией и ref-квалификатором (если есть) в базовом классе (а не противоречивый). Такие скрытые или переопределенные объявления исключаются из набора объявлений, введенных с помощью-объявления.
Таким образом, как для аргумента шаблона, так и для возвращаемого типа методы скрыты в следующем сценарии:
struct Base
{
template <std::size_t I, std::enable_if_t<I == 0>* = nullptr>
void f() {}
template <std::size_t I>
std::enable_if_t<I == 0> g() {}
};
struct S : Base
{
using Base::f; // Useless, f<0> is still hidden
using Base::g; // Useless, g<0> is still hidden
template <std::size_t I, std::enable_if_t<I == 1>* = nullptr>
void f() {}
template <std::size_t I>
std::enable_if_t<I == 1> g() {}
};
демонстрация (gcc неправильно находит базовую функцию).
В то время как с аргументом, аналогичный сценарий работает:
struct Base
{
template <std::size_t I>
void h(std::enable_if_t<I == 0>* = nullptr) {}
};
struct S : Base
{
using Base::h; // Base::h<0> is visible
template <std::size_t I>
void h(std::enable_if_t<I == 1>* = nullptr) {}
};