Vi la charla de Walter Brown en Cppcon14 sobre la programación de plantillas modernas ( Parte I , Parte II ) donde presentó su void_t
técnica SFINAE.
Ejemplo:
Dada una plantilla variable simple que evalúa void
si todos los argumentos de la plantilla están bien formados:
template< class ... > using void_t = void;
y el siguiente rasgo que verifica la existencia de una variable miembro llamada miembro :
template< class , class = void >
struct has_member : std::false_type
{ };
// specialized as has_member< T , void > or discarded (sfinae)
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : std::true_type
{ };
Traté de entender por qué y cómo funciona esto. Por lo tanto, un pequeño ejemplo:
class A {
public:
int member;
};
class B {
};
static_assert( has_member< A >::value , "A" );
static_assert( has_member< B >::value , "B" );
1) has_member< A >
has_member< A , void_t< decltype( A::member ) > >
A::member
existedecltype( A::member )
está bien formadovoid_t<>
es válido y se evalúa comovoid
has_member< A , void >
y por eso elige la plantilla especializadahas_member< T , void >
y evalúa atrue_type
2) has_member< B >
has_member< B , void_t< decltype( B::member ) > >
B::member
no existedecltype( B::member )
está mal formado y falla en silencio (sfinae)has_member< B , expression-sfinae >
entonces esta plantilla se descarta
- el compilador encuentra
has_member< B , class = void >
con vacío como argumento predeterminado has_member< B >
evalúa afalse_type
Preguntas:
1. ¿Es correcto entender esto?
2. Walter Brown afirma que el argumento predeterminado tiene que ser exactamente del mismo tipo que el utilizado void_t
para que funcione. ¿Porqué es eso? (No veo por qué estos tipos deben coincidir, ¿no funciona cualquier tipo predeterminado?)
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
.has_member< T , class = void >
incumplimientovoid
. Suponiendo que este rasgo se usará solo con 1 argumento de plantilla en cualquier momento, entonces el argumento predeterminado podría ser de cualquier tipo.template <class, class = void>
atemplate <class, class = void_t<>>
. Así que ahora somos libres de hacer lo que queramos convoid_t
la implementación de plantillas de alias :)Respuestas:
1. Plantilla de clase primaria
Cuando escribe
has_member<A>::value
, el compilador busca el nombrehas_member
y encuentra la plantilla de clase primaria , es decir, esta declaración:(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 escritohas_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:El compilador intenta hacer coincidir los argumentos de la plantilla
A, void
con los patrones definidos en la especialización parcial:T
yvoid_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 plantillaT
. Esta es una deducción trivial, pero considere un patrón comoT const&
, donde aún podríamos deducirT
. Para el patrónT
y el argumento de plantillaA
, deducimosT
serA
.En el segundo patrón
void_t< decltype( T::member ) >
, el parámetro de plantillaT
aparece en un contexto donde no puede deducirse de ningún argumento de plantilla.Deducción argumento de plantilla está terminado (*) , ahora las deducidas argumentos de plantilla son sustituidos. Esto crea una especialización que se ve así:
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: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:
Terminamos con la misma especialización:
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:
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.
fuente
Esa especialización anterior solo existe cuando está bien formada, por lo que
decltype( T::member )
es válida y no ambigua. La especialización es asíhas_member<T , void>
como el estado en el comentario.Cuando escribe
has_member<A>
, sehas_member<A, void>
debe al argumento de plantilla predeterminado.Y tenemos especialización para
has_member<A, void>
(heredar detrue_type
) pero no tenemos especialización parahas_member<B, void>
(así que usamos la definición predeterminada: heredar defalse_type
)fuente