1. Modelo de Classe Primária
Quando você escreve has_member<A>::value
, o compilador consulta o nome has_member
e encontra o modelo de classe principal , ou seja, esta declaração:
template< class , class = void >
struct has_member;
(No OP, isso é escrito como uma definição.)
A lista de argumentos do modelo <A>
é comparada à lista de parâmetros do modelo deste modelo principal. Desde o modelo principal tem dois parâmetros, mas só fornecido um, o parâmetro restante é padrão para o argumento de modelo padrão: void
. É como se você tivesse escrito has_member<A, void>::value
.
2. Modelo de Classe Especializada
Agora , a lista de parâmetros do modelo é comparada com qualquer especialização do modelo has_member
. Somente se nenhuma especialização corresponder, a definição do modelo primário será usada como fallback. Portanto, a especialização parcial é levada em consideração:
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };
O compilador tenta combinar os argumentos do modelo A, void
com os padrões definidos na especialização parcial: T
e void_t<..>
um por um. Primeiro , é realizada a dedução do argumento do modelo. A especialização parcial acima ainda é um modelo com parâmetros de modelo que precisam ser "preenchidos" por argumentos.
O primeiro padrão T
, permite que o compilador deduza o parâmetro-modelo T
. Esta é uma dedução trivial, mas considere um padrão como T const&
, onde ainda podemos deduzir T
. Para o padrão T
e o argumento do modelo A
, deduzimos T
ser A
.
No segundo padrão void_t< decltype( T::member ) >
, o parâmetro do modelo T
aparece em um contexto em que não pode ser deduzido de nenhum argumento do modelo.
Há duas razões para isso:
A expressão interna decltype
é explicitamente excluída da dedução do argumento do modelo. Eu acho que isso é porque pode ser arbitrariamente complexo.
Mesmo se usarmos um padrão sem decltype
like void_t< T >
, a dedução de T
acontecerá no modelo de alias resolvido. Ou seja, resolvemos o modelo de alias e depois tentamos deduzir o tipo T
do padrão resultante. O padrão resultante, no entanto, é o void
qual não é dependente T
e, portanto, não nos permite encontrar um tipo específico para T
. Isso é semelhante ao problema matemático de tentar inverter uma função constante (no sentido matemático desses termos).
A dedução do argumento do modelo está concluída (*) , agora os argumentos do modelo deduzido são substituídos. Isso cria uma especialização que se parece com isso:
template<>
struct has_member< A, void_t< decltype( A::member ) > > : true_type
{ };
O tipo void_t< decltype( A::member ) >
agora pode ser avaliado. É bem formado após a substituição, portanto, nenhuma falha de substituição ocorre. Nós temos:
template<>
struct has_member<A, void> : true_type
{ };
3. Escolha
Agora , podemos comparar a lista de parâmetros do modelo desta especialização com os argumentos do modelo fornecidos ao original has_member<A>::value
. Ambos os tipos correspondem exatamente, portanto, essa especialização parcial é escolhida.
Por outro lado, quando definimos o modelo como:
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
{ };
Terminamos com a mesma especialização:
template<>
struct has_member<A, void> : true_type
{ };
mas a nossa lista de argumentos de modelos por has_member<A>::value
enquanto é <A, int>
. Os argumentos não correspondem aos parâmetros da especialização, e o modelo primário é escolhido como substituto.
(*) O Padrão, IMHO, confuso, inclui o processo de substituição e a correspondência de argumentos de modelo explicitamente especificados no processo de dedução de argumento de modelo . Por exemplo (pós-N4296) [temp.class.spec.match] / 2:
Uma especialização parcial corresponde a uma determinada lista de argumentos do modelo real se os argumentos do modelo da especialização parcial puderem ser deduzidos da lista de argumentos do modelo real.
Mas isso não significa apenas que todos os parâmetros-modelo da especialização parcial devem ser deduzidos; isso também significa que a substituição deve ter sucesso e (como parece?) os argumentos do modelo devem corresponder aos parâmetros (substituídos) do modelo da especialização parcial. Observe que não estou completamente ciente de onde o Padrão especifica a comparação entre a lista de argumentos substituídos e a lista de argumentos fornecida.
has_member<A,int>::value
. Em seguida, a especialização parcial avaliadahas_member<A,void>
não pode corresponder. Portanto, ele precisa serhas_member<A,void>::value
, ou, com açúcar sintático, um argumento padrão do tipovoid
.