1. Plantilla de clase primaria
Cuando escribe has_member<A>::value
, el compilador busca el nombre has_member
y encuentra la plantilla de clase primaria , es decir, esta declaración:
template< class , class = void >
struct has_member;
(En el OP, eso está escrito como una definición).
La lista de argumentos de plantilla <A>
se compara con la lista de parámetros de plantilla de esta plantilla primaria. Dado que la plantilla principal tiene dos parámetros, pero sólo se suministra uno, el parámetro restante está en mora al argumento de plantilla por defecto: void
. Es como si hubieras escrito has_member<A, void>::value
.
2. Plantilla de clase especializada
Ahora , la lista de parámetros de la plantilla se compara con cualquier especialización de la plantilla has_member
. Solo si no coincide la especialización, la definición de la plantilla primaria se usa como una alternativa. Entonces, la especialización parcial se tiene en cuenta:
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };
El compilador intenta hacer coincidir los argumentos de la plantilla A, void
con los patrones definidos en la especialización parcial: T
y void_t<..>
uno por uno. Primero , se realiza la deducción de argumento de plantilla. La especialización parcial anterior sigue siendo una plantilla con parámetros de plantilla que deben "rellenarse" con argumentos.
El primer patrón T
, permite al compilador deducir el parámetro de plantilla T
. Esta es una deducción trivial, pero considere un patrón como T const&
, donde aún podríamos deducir T
. Para el patrón T
y el argumento de plantilla A
, deducimos T
ser A
.
En el segundo patrón void_t< decltype( T::member ) >
, el parámetro de plantilla T
aparece en un contexto donde no puede deducirse de ningún argumento de plantilla.
Hay dos razones para esto:
La expresión dentro decltype
está explícitamente excluida de la deducción de argumentos de plantilla. Supongo que esto se debe a que puede ser arbitrariamente complejo.
Incluso si usamos un patrón sin decltype
like void_t< T >
, la deducción T
ocurre en la plantilla de alias resuelto. Es decir, resolvemos la plantilla de alias y luego intentamos deducir el tipo T
del patrón resultante. Sin embargo, el patrón resultante es el void
que no depende T
y, por lo tanto, no nos permite encontrar un tipo específico para T
. Esto es similar al problema matemático de tratar de invertir una función constante (en el sentido matemático de esos términos).
Deducción argumento de plantilla está terminado (*) , ahora las deducidas argumentos de plantilla son sustituidos. Esto crea una especialización que se ve así:
template<>
struct has_member< A, void_t< decltype( A::member ) > > : true_type
{ };
El tipo void_t< decltype( A::member ) >
ahora se puede evaluar. Está bien formado después de la sustitución, por lo tanto, no ocurre una falla de sustitución . Obtenemos:
template<>
struct has_member<A, void> : true_type
{ };
3. Elección
Ahora , podemos comparar la lista de parámetros de plantilla de esta especialización con los argumentos de plantilla suministrados al original has_member<A>::value
. Ambos tipos coinciden exactamente, por lo que se elige esta especialización parcial.
Por otro lado, cuando definimos la plantilla 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 con la misma especialización:
template<>
struct has_member<A, void> : true_type
{ };
pero nuestra lista de argumentos de plantilla por has_member<A>::value
ahora es <A, int>
. Los argumentos no coinciden con los parámetros de la especialización, y la plantilla primaria se elige como una alternativa.
(*) La Norma, en mi humilde opinión, incluye el proceso de sustitución y la coincidencia de argumentos de plantilla especificados explícitamente en el proceso de deducción de argumentos de plantilla . Por ejemplo (post-N4296) [temp.class.spec.match] / 2:
Una especialización parcial coincide con una lista de argumentos de plantilla real dada si los argumentos de plantilla de la especialización parcial pueden deducirse de la lista de argumentos de plantilla real.
Pero esto no solo significa que todos los parámetros de plantilla de la especialización parcial deben deducirse; también significa que la sustitución debe tener éxito y (como parece) los argumentos de la plantilla tienen que coincidir con los parámetros de la plantilla (sustituidos) de la especialización parcial. Tenga en cuenta que no estoy completamente al tanto de dónde el Estándar especifica la comparación entre la lista de argumentos sustituidos y la lista de argumentos suministrada.
has_member<A,int>::value
. Entonces, la especialización parcial que evalúahas_member<A,void>
no puede coincidir. Por lo tanto, debe serhas_member<A,void>::value
, o, con azúcar sintáctico, un argumento de tipo predeterminadovoid
.