Ответ 1
Когда вы пишете has_member<A>::value
, компилятор просматривает имя has_member
и находит шаблон первичного класса, то есть это объявление:
template< class , class = void >
struct has_member;
(В OP, написанном как определение.)
Список аргументов шаблона <A>
сравнивается со списком параметров шаблона этого первичного шаблона. Поскольку первичный шаблон имеет два параметра, но вы только поставляете один, оставшийся параметр по умолчанию имеет аргумент шаблона по умолчанию: void
. Как будто вы написали has_member<A, void>::value
.
Теперь список параметров шаблона сравнивается с любыми специализациями шаблона has_member
. Только если никакая специализация не совпадает, определение первичного шаблона используется как спад. Поэтому учитывается частичная специализация:
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };
Компилятор пытается сопоставить аргументы шаблона A, void
с шаблонами, определенными в частичной специализации: T
и void_t<..>
один за другим. Сначала выполняется вывод аргумента шаблона. Частичная специализация выше - это шаблон с параметрами шаблона, которые должны быть "заполнены" аргументами.
Первый шаблон T
позволяет компилятору вывести шаблон-параметр T
. Это тривиальный вывод, но рассмотрим шаблон, подобный T const&
, где мы могли бы вывести T
. Для шаблона T
и аргумента шаблона A
мы выводим T
как A
.
Во втором шаблоне void_t< decltype( T::member ) >
параметр шаблона T
появляется в контексте, где его невозможно вывести из любого аргумента шаблона. Для этого есть две причины:
-
Выражение внутри
decltype
явно исключено из вывода аргумента шаблона. Я думаю, это потому, что он может быть сколь угодно сложным. -
Даже если мы использовали шаблон без
decltype
какvoid_t< T >
, то выводT
происходит в шаблоне разрешенных алиасов. То есть мы разрешаем шаблон псевдонима, а затем пытаемся вывести типT
из полученного шаблона. Получаемый шаблон, однако,void
, который не зависит отT
и поэтому не позволяет нам найти определенный тип дляT
. Это похоже на математическую проблему попытки инвертировать постоянную функцию (в математическом смысле этих терминов).
Вывод аргумента шаблона завершен (*) теперь выведенные аргументы шаблона заменяются. Это создает специализацию, которая выглядит так:
template<>
struct has_member< A, void_t< decltype( A::member ) > > : true_type
{ };
Теперь можно оценить тип void_t< decltype( A::member ) > >
. Он хорошо формируется после замещения, следовательно, не происходит замещения. Получаем:
template<>
struct has_member<A, void> : true_type
{ };
Теперь мы можем сравнить список параметров шаблона этой специализации с аргументами шаблона, предоставленными исходному has_member<A>::value
. Оба типа соответствуют точно, поэтому эта частичная специализация выбрана.
С другой стороны, когда мы определяем шаблон как:
template< class , class = int > // <-- int here instead of void
struct has_member : false_type
{ };
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };
В итоге получим ту же специализацию:
template<>
struct has_member<A, void> : true_type
{ };
но список шаблонов шаблонов для has_member<A>::value
теперь равен <A, int>
. Аргументы не соответствуют параметрам специализации, а основной шаблон выбирается как спад.
(*) Стандарт, IMHO смутно, включает в себя процесс подстановки и сопоставление явно заданных аргументов шаблона в процессе вычитания аргумента шаблона. Например (post-N4296) [temp.class.spec.match]/2:
Частичная специализация соответствует данному фактическому списку аргументов шаблона если шаблонные аргументы частичной специализации могут быть выведены из фактического списка аргументов шаблона.
Но это не просто означает, что все шаблонные параметры частичной специализации должны быть выведены; это также означает, что замена должна быть успешной и (как кажется?) аргументы шаблона должны соответствовать (замененным) параметрам шаблона частичной специализации. Обратите внимание, что я не совсем понимаю, где стандарт указывает сравнение между списком замещенных аргументов и предоставленным списком аргументов.