Из статьи cppreference.com на std::enable_if
,
Примечания
Распространенной ошибкой является объявление двух шаблонов функций, которые отличаются только аргументами шаблона по умолчанию. Это недопустимо, поскольку аргументы шаблона по умолчанию не являются частью сигнатуры шаблона функции, и объявление двух разных шаблонов функций с одной и той же сигнатурой недопустимо.
/*** WRONG ***/
struct T {
enum { int_t,float_t } m_type;
template <
typename Integer,
typename = std::enable_if_t<std::is_integral<Integer>::value>
>
T(Integer) : m_type(int_t) {}
template <
typename Floating,
typename = std::enable_if_t<std::is_floating_point<Floating>::value>
>
T(Floating) : m_type(float_t) {} // error: cannot overload
};
/* RIGHT */
struct T {
enum { int_t,float_t } m_type;
template <
typename Integer,
typename std::enable_if_t<std::is_integral<Integer>::value, int> = 0
>
T(Integer) : m_type(int_t) {}
template <
typename Floating,
typename std::enable_if_t<std::is_floating_point<Floating>::value, int> = 0
>
T(Floating) : m_type(float_t) {} // OK
};
Мне трудно понять, почему версия *** WRONG ***
не компилируется, а версия *** RIGHT***
- компилируется. Объяснение и пример для меня - это карго-культ. Все, что было сделано выше, - это изменить параметр шаблона типа на параметр шаблона без типа. Для меня обе версии должны быть действительными, потому что обе полагаются на std::enable_if<boolean_expression,T>
, имеющий член typedef с именем type
, а std::enable_if<false,T>
не имеет такого члена. Ошибка замены (что не является ошибкой) должна привести к обеим версиям.
Глядя на стандарт, он говорит, что в [temp.deduct], что
при ссылке на специализацию шаблона функции все аргументы шаблона должны иметь значения
а позже это
если аргумент шаблона не был выведен и соответствующий ему параметр шаблона имеет аргумент по умолчанию, аргумент шаблона определяется путем замены аргументов шаблона, определенных для предшествующих параметров шаблона, в аргумент по умолчанию. Если замена приводит к недопустимому типу, как описано выше, определение типа не выполняется.
То, что этот сбой при выводе типа не обязательно является ошибкой, - вот в чем суть SFINAE.
Почему изменение параметра шаблона typename в версии *** WRONG ***
на параметр, отличное от typename, делает версию *** RIGHT ***
"правильной"?
В основном потому, что [temp.over.link] / 6 не поговорим об аргументе шаблона по умолчанию: