Cómo funciona` void t'


Vi la charla de Walter Brown en Cppcon14 sobre programación moderna de plantillas ( Parte I, Parte II ) donde presentó su técnica void_t SFINAE.

Ejemplo:
Dada una plantilla de variable simple que evalúa a void si todos los argumentos de la plantilla están bien formados:

template< class ... > using void_t = void;

Y el siguiente rasgo que comprueba 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 existe
    • decltype( A::member ) está bien formado
    • void_t<> es válido y evalúa a void
  • has_member< A , void > y por lo tanto elige la plantilla especializada
  • has_member< T , void > y evalúa a true_type

2. has_member< B >

  • has_member< B , void_t< decltype( B::member ) > >
    • B::member no existe
    • decltype( B::member ) está mal formado y falla silenciosamente (sfinae)
    • has_member< B , expression-sfinae > así que esta plantilla se descarta
  • el compilador encuentra has_member< B , class = void > con void como argumento por defecto
  • has_member< B > evalúa a false_type

Http://ideone.com/HCTlBb

Preguntas:
1. ¿Mi entendimiento de esto es correcto?
2. Walter Brown afirma que el argumento por defecto tiene que ser exactamente el mismo tipo que el usado en void_t para que funcione. ¿Por qué es eso? (No veo por qué este tipo necesita coincidir, no solo cualquier tipo predeterminado hace el trabajo?)

Author: ACyclic, 2014-12-29

2 answers

Cuando escribe has_member<A>::value, el compilador busca el nombre has_member y encuentra la plantilla de clase primary , 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 la plantilla <A> se compara con la lista de parámetros de la plantilla de esta plantilla primaria. Dado que la plantilla principal tiene dos parámetros, pero solo proporcionó uno, el parámetro restante se encuentra en el parámetro predeterminado de la plantilla: void. Es como si tuvieras escrito has_member<A, void>::value.

Ahora, la lista de parámetros de la plantilla se compara con cualquier especialización de la plantilla has_member. Solo si ninguna especialización coincide, la definición de la plantilla primaria se utiliza como alternativa. Así que 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 del argumento de plantilla. El la especialización parcial anterior sigue siendo una plantilla con parámetros de plantilla que necesitan ser "rellenados" por 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 todavía podríamos deducir T. Para el patrón T y el argumento de plantilla A, deducimos que T a A.

En el segundo patrón void_t< decltype( T::member ) >, el parámetro-plantilla T aparece en un contexto donde no se puede deducir de ningún argumento de plantilla. Hay dos razones para esto:

  • La expresión dentro de 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 como void_t< T >, entonces la deducción de T ocurre en la plantilla de alias resuelta. Es decir, resolvemos la plantilla de alias y luego tratamos de deducir el tipo T del resultado patrón. Sin embargo, el patrón resultante es void, que no depende de T y por lo tanto no nos permite encontrar un tipo específico para T. Esto es similar al problema matemático de intentar invertir una función constante (en el sentido matemático de esos términos).

La deducción del argumento de plantilla ha finalizado(*), ahora se sustituyen los argumentos de plantilla deducidos. Esto crea una especialización que se ve así:

template<>
struct has_member< A, void_t< decltype( A::member ) > > : true_type
{ };

El ahora se puede evaluar el tipo void_t< decltype( A::member ) > >. Está bien formado después de la sustitución, por lo tanto, no Falla de sustitución ocurre. Obtenemos:

template<>
struct has_member<A, void> : true_type
{ };

Ahora, podemos comparar la lista de parámetros de plantilla de esta especialización con los argumentos de plantilla suministrados al has_member<A>::value original. 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 para has_member<A>::value ahora es <A, int>. Los argumentos no coinciden con los parámetros de la especialización, y la plantilla principal se elige como una alternativa.


(*) El Estándar, en mi humilde opinión, incluye el proceso de sustitución y la coincidencia de argumentos de plantilla explícitamente especificados en el proceso deducción de argumento de plantilla. Por ejemplo (post-N4296) [temp.clase.spec.partido] / 2:

Una especialización parcial coincide con una lista de argumentos de plantilla dada si se pueden deducir los argumentos de plantilla de la especialización parcial de la lista de argumentos de plantilla actual.

Pero esto no significa solo que todos los parámetros-plantilla de la especialización parcial tienen que ser deducidos; 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 soy completamente consciente de donde el Estándar especifica la comparación entre la lista de argumentos sustituida y la lista de argumentos suministrada.

 101
Author: dyp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-21 11:54:07
// specialized as has_member< T , void > or discarded (sfinae)
template<class T>
struct has_member<T , void_t<decltype(T::member)>> : true_type
{ };

Que la especialización anterior existe solo cuando está bien formada, por lo que cuando decltype( T::member ) es válida y no ambigua. la especialización es así para has_member<T , void> como estado en el comentario.

Cuando escribe has_member<A>, es has_member<A, void> debido al argumento de plantilla predeterminado.

Y tenemos especialización para has_member<A, void> (así que heredamos de true_type) pero no tenemos especialización para has_member<B, void> (así que usamos la definición predeterminada : heredar de false_type)

 16
Author: Jarod42,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-29 11:16:13