c++ - studio - wp_nav_menu li class




¿Cómo funciona `void_t` (2)

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

Ejemplo:
Dada una plantilla variable simple que se evalúa como 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 existe
    • decltype( A::member ) está bien formado
    • void_t<> es válido y se evalúa como void
  • has_member< A , void > y, por lo tanto, elige la plantilla especializada
  • has_member< T , void > y se evalúa como 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 en silencio (sfinae)
    • has_member< B , expression-sfinae > por lo que esta plantilla se descarta
  • el compilador encuentra has_member< B , class = void > con void como argumento predeterminado
  • has_member< B > evalúa como false_type

http://ideone.com/HCTlBb

Preguntas:
1. ¿Es correcto entender esto?
2. Walter Brown afirma que el argumento predeterminado debe ser exactamente del mismo tipo que el utilizado en void_t para que funcione. ¿Porqué es eso? (No veo por qué estos tipos deben coincidir, ¿no funciona cualquier tipo predeterminado?)


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 solo proporcionó uno, el parámetro restante está predeterminado en el argumento de plantilla predeterminado: void . Es como si hubiera 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 no coincide la especialización, la definición de la plantilla primaria se utiliza 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 que T es 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 de decltype se excluye explícitamente 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 tipo de decltype como void_t< T > , la deducción de 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 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 tratar de invertir una función constante (en el sentido matemático de esos términos).

La deducción de argumentos 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 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
{ };

Ahora, podemos comparar la lista de parámetros de plantilla de esta especialización con los argumentos de plantilla suministrados al valor 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 para 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 se deben deducir todos los parámetros de plantilla de la especialización parcial; también significa que la sustitución debe tener éxito y (como parece) los argumentos de la plantilla deben coincidir con los parámetros de la plantilla (sustituidos) de la especialización parcial. Tenga en cuenta que no soy completamente consciente de dónde el Estándar especifica la comparación entre la lista de argumentos sustituidos y la lista de argumentos suministrada.


// specialized as has_member< T , void > or discarded (sfinae)
template<class T>
struct has_member<T , void_t<decltype(T::member)>> : true_type
{ };

Esa especialización anterior solo existe cuando está bien formada, por lo que cuando decltype( T::member ) es válido y no ambiguo. 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 hereda de true_type ) pero no tenemos especialización para has_member<B, void> (así que usamos la definición predeterminada: heredar de false_type )





sfinae