Ответ 1
has_foo<unsigned>::value
- это независимое выражение, поэтому оно немедленно запускает экземпляр has_foo<unsigned>
(даже если соответствующая специализация никогда не используется).
Соответствующие правила: [temp.point]/1:
Для специализации шаблона функции, специализации шаблона функции-члена или специализации для функции-члена или статического элемента данных шаблона класса, если специализация неявно создается, потому что на нее ссылаются из другой специализированной специализации и контекста из на который он ссылается, зависит от параметра шаблона, точкой инстанцирования специализации является точка инстанцирования охватывающей специализации. В противном случае точка инстанцирования для такой специализации сразу же следует за объявлением или определением области пространства имен, которое относится к специализации.
(обратите внимание, что мы находимся здесь в независящем случае) и [temp.res]/8:
Программа плохо сформированный, не требуется диагностика, если:
- [...]
- гипотетическое создание шаблона сразу после его определения будет плохо сформировано из-за конструкции, которая не зависит от параметра шаблона, или
- интерпретация такой конструкции в гипотетическом экземпляре отличается от интерпретации соответствующей конструкции в любой фактической инстанцировании шаблона.
Эти правила призваны предоставить свободу реализации для создания экземпляра has_foo<unsigned>
в том месте, где оно появляется в приведенном выше примере, и дать ему ту же семантику, как если бы она была создана там. (Обратите внимание, что правила здесь на самом деле неверно: точка экземпляра для объекта, на который ссылается объявление другого объекта, фактически должна непосредственно предшествовать этому объекту, а не сразу после него. Об этом сообщается в качестве основной проблемы, но она не включена список проблем пока не был обновлен на некоторое время.)
Как следствие, точка инстанцирования has_foo
в частичной специализации с плавающей запятой происходит до точки объявления этой специализации, которая после >
частичной специализации в [basic.scope.pdecl ]/3:
Точка объявления для шаблона класса или класса, сначала объявленного спецификатором класса, сразу после идентификатора или простого шаблона-идентификатора (если есть) в его классе-главе (раздел 9).
Поэтому, когда вызов foo
из has_foo<unsigned>
просматривает частичные специализации foo_impl
, он вообще не находит специализацию с плавающей запятой.
Несколько других заметок о вашем примере:
1) Использование cast-to- void
в операторе запятой:
static auto test(T1 x) -> decltype(foo(x),void(),yes{});
Это плохой шаблон. operator,
поиск по-прежнему выполняется для оператора запятой, где один из его операндов имеет тип класса или перечисления (хотя он никогда не сможет добиться успеха). Это может привести к выполнению ADL [реализация разрешена, но не требуется пропустить это), которая запускает создание всех связанных классов возвращаемого типа foo (в частности, если foo
возвращает unique_ptr<X<T>>
), это может вызвать создание X<T>
и может сделать программу плохо сформированной, если эта инстанция не работает из этой единицы перевода). Вы должны отдать все операнды оператора запятой пользовательского типа на void
:
static auto test(T1 x) -> decltype(void(foo(x)),yes{});
2) Идиома SFINAE:
template <typename T1>
static auto test(T1 x) -> decltype(void(foo(x)),yes{});
static no test(...);
static const bool value = std::is_same<yes,decltype(test(std::declval<T>()))>::value;
Это не правильный шаблон SFINAE в общем случае. Здесь есть несколько проблем:
- Если
T
- это тип, который нельзя передать как аргумент, напримерvoid
, вы вызываете жесткую ошибку вместоvalue
, оцениваяfalse
как предполагалось - Если
T
- это тип, с которым ссылка не может быть сформирована, вы снова вызываете жесткую ошибку. - вы проверяете, может ли
foo
применяться к lvalue типаremove_reference<T>
, даже еслиT
является ссылкой rvalue
Лучшее решение состоит в том, чтобы поместить всю проверку в версию yes
test
вместо разделения фрагмента declval
на value
:
template <typename T1>
static auto test(int) -> decltype(void(foo(std::declval<T1>())),yes{});
template <typename>
static no test(...);
static const bool value = std::is_same<yes,decltype(test<T>(0))>::value;
Этот подход более естественно распространяется на ранжированный набор опций:
// elsewhere
template<int N> struct rank : rank<N-1> {};
template<> struct rank<0> {};
template <typename T1>
static no test(rank<2>, std::enable_if_t<std::is_same<T1, double>::value>* = nullptr);
template <typename T1>
static yes test(rank<1>, decltype(foo(std::declval<T1>()))* = nullptr);
template <typename T1>
static no test(rank<0>);
static const bool value = std::is_same<yes,decltype(test<T>(rank<2>()))>::value;
Наконец, ваш тип будет быстрее оцениваться и использовать меньше памяти во время компиляции, если вы переместите вышеприведенные объявления test
вне определения has_foo
(возможно, в некоторый вспомогательный класс или пространство имен); таким образом, для каждого использования has_foo
им не требуется избыточно создавать экземпляры один раз.