Ответ 1
Аргументы по умолчанию (как аргументы функции по умолчанию или аргументы шаблона по умолчанию) не являются частью сигнатуры функции. Вам разрешено определять только одну функцию с заданной подписью.
В примере 1 кода, если мы отбросим имена аргументов и все значения по умолчанию, у нас есть две функции:
template <typename T, std::enable_if_t<!std::is_integral<T>::value, detail::enabler>>
void func(T ) { ... }
template <typename T, std::enable_if_t<std::is_integral<T>::value, detail::enabler>>
void func(T ) { ... }
Это две разные подписи - второй параметр шаблона имеет разные типы в обеих функциях, поэтому он действителен с этой точки зрения.
Теперь, что происходит, когда мы на самом деле называем это. Если T
является интегральным типом, второй аргумент заменяется в обоих случаях:
template <typename T, ????> void func(T ) { ... }
template <typename T, detail::enabler = dummy> void func(T ) { ... }
В первой функции выражение typename std::enable_if<false, detail::enabler>::type
плохо сформировано. В этом типе нет type
typedef. Как правило, запись плохо сформированного кода является трудной ошибкой компилятора. Но, благодаря правилу, называемому SFINAE (Ошибка замещения не является ошибкой), плохо сформированный код, возникающий в непосредственном контексте замены шаблона, не является ошибкой - он просто заставляет исключить специализацию шаблона класса/класса из набора, Таким образом, мы получаем только один действительный:
template <typename T, detail::enabler = dummy> void func(T ) { ... }
который вызывается.
Однако в примере кода 2 мы имеем две функции:
template <typename T, typename>
void func(T ) { ... }
template <typename T, typename>
void func(T ) { ... }
Те же! Мы определяем одну и ту же функцию дважды - это недопустимо, следовательно, ошибка. Это ошибка по той же причине, что:
int foo(int x = 1) { return x; }
int foo(int x = 2) { return x; }
- ошибка.
Вот почему Xeo использовал (неконструктивное) перечисление для включения, если начать, - это упрощает запись специализированных специализированных шаблонов, поскольку вы можете сделать это с перечислением, но вы не можете делать это с помощью типов.
Обратите внимание, что вы могли бы аналогичным образом сделать то же самое с чем-то вроде int
:
template <class T, std::enable_if_t<std::is_integral<T>::value, int> = 0>
void func(T ) { ... }
Но это позволило бы злобному пользователю предоставить значение для этого аргумента (в этом конкретном контексте это не имело бы значения, но это могло бы быть и в других). При detail::enabler
такое значение не может быть предоставлено.