tontería
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 de 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 un miembro de datos llamado 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 lo tanto 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 >
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 afalse_type
Preguntas:
- ¿Es correcto mi entendimiento de esto?
- Walter Brown afirma que el argumento predeterminado tiene que ser exactamente del mismo tipo que el que se usa en
void_t
para que funcione. ¿Porqué es eso? (No veo por qué estos tipos deben coincidir, ¿cualquier tipo predeterminado no hace el trabajo?)
mojar
1. Plantilla de clase primaria
Cuando escribes has_member<A>::value
el compilador busca el nombre has_member
y encuentra el primario plantilla de clase, 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 plantilla de esta plantilla principal. Dado que la plantilla principal tiene dos parámetros, pero usted solo proporcionó uno, el parámetro restante tiene como valor predeterminado el argumento de la plantilla predeterminada: void
. Es como si hubieras escrito has_member<A, void>::value
.
2. Plantilla de clase especializada
Ahorala 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 principal se usa como alternativa. Por lo que se tiene en cuenta la especialización parcial:
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 a uno. Primero, se realiza la deducción del argumento de la plantilla. La especialización parcial anterior sigue siendo una plantilla con parámetros de plantilla que deben “llenarse” 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 la 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 en el que no se puede deducir de ningún argumento de plantilla.
Hay dos razones para esto:
La expresión interior
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
decltype
comovoid_t< T >
entonces la deducción deT
ocurre en la plantilla de alias resuelta. Es decir, resolvemos la plantilla de alias y luego tratamos de deducir el tipoT
del patrón resultante. El patrón resultante, sin embargo, esvoid
que no depende deT
y por lo tanto no nos permite encontrar un tipo específico paraT
. 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 la plantilla ha terminado. , ahora el deducido
template<>
struct has_member< A, void_t< decltype( A::member ) > > : true_type
{ };
se sustituyen los argumentos de la plantilla. Esto crea una especialización que se ve así: void_t< decltype( A::member ) >
El tipo ya se puede evaluar. Está bien formado después de la sustitución, por lo tanto, no Fallo de sustitución
template<>
struct has_member<A, void> : true_type
{ };
ocurre. Obtenemos:
3. ElecciónAhora has_member<A>::value
podemos comparar la lista de parámetros de plantilla de esta especialización con los argumentos de plantilla suministrados al original
. Ambos tipos coinciden exactamente, por lo que se elige esta especialización parcial.
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
{ };
Por otro lado, cuando definimos la plantilla como:
template<>
struct has_member<A, void> : true_type
{ };
Terminamos con la misma especialización: has_member<A>::value
pero nuestra lista de argumentos de plantilla para <A, int>
ahora es
. Los argumentos no coinciden con los parámetros de la especialización y la plantilla principal se elige como alternativa. El Estándar, en mi humilde opinión de manera confusa, incluye el proceso de sustitución y la coincidencia de argumentos de plantilla explícitamente especificados en el [temp.class.spec.match]deducción de argumento de plantilla
proceso. Por ejemplo (posterior a N4296)
/2: Una especialización parcial coincide con una lista de argumentos de plantilla real determinada si los argumentos de plantilla de la especialización parcial se pueden deducir de la lista de argumentos de plantilla real. Pero esto no justo significa que todos los parámetros de 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 deben coincidir con los parámetros de la plantilla (sustituida) 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 proporcionada.
¡Gracias! Lo he leído una y otra vez, y supongo que mi pensamiento sobre cómo funciona exactamente la deducción de argumentos de plantilla y qué elige el compilador para la plantilla final no es correcto en este momento.
-
– tonterías
30 de diciembre de 2014 a las 12:12
@JohannesSchaub-litb ¡Gracias! Aunque eso es un poco deprimente. ¿Realmente no hay reglas para hacer coincidir un argumento de plantilla con una especialización? ¿Ni siquiera para especializaciones explícitas?
-
– mojar 30 de diciembre de 2014 a las 16:27
W/r/t argumentos de plantilla predeterminados,
open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2008
-
– CT 28 de enero de 2015 a las 10:41 @dyp Unas semanas más tarde y leyendo mucho sobre esto y con una pista de
este fragmento
Creo que empiezo a entender cómo funciona esto. Tu explicación hace que de lectura en lectura tenga más sentido para mí, ¡gracias!
-
– tonterías 25 de febrero de 2015 a las 23:05 Quería agregar que el término
primario
la plantilla fue la clave (las plantillas se encuentran por primera vez en el código)
// specialized as has_member< T , void > or discarded (sfinae)
template<class T>
struct has_member<T , void_t<decltype(T::member)>> : true_type
{ };
– tontería decltype( T::member )
27/09/2016 a las 12:51 has_member<T , void>
Esa especialización anterior existe solo cuando está bien formada, por lo que cuando
es válido y no ambiguo. la especialización es así para has_member<A>
como se indica en el comentario. has_member<A, void>
Cuando escribes
es has_member<A, void>
debido al argumento de la plantilla predeterminada. true_type
Y tenemos especialización para has_member<B, void>
(así que heredar de false_type
) pero no tenemos especialización para
-
(así que usamos la definición predeterminada: heredar de
void_t<decltype(T::member)>>
)decltype(T::member, void())
Entoncesno es más que una forma estandarizada/más clara de escribir
?
-
– 303
void_t
5 de diciembre de 2021 a las 21:26@ 303: alguna versión (antigua) del compilador tuvo dificultades para aplicar SFINAE con (algunas implementaciones de)
aunque.
6 de diciembre de 2021 a las 8:41
avatar de usuario de user3059627
usuario3059627 Este hilo y el hilo SFINAE: Entender void_t y detectar_si me salvaron. Quiero demostrar el comportamiento con algunos ejemplos:
La herramienta:
struct A {
using type = int;
};
struct B{
using type = void;
}
cppinsights
auto f = has_type_member<float>::value;
auto a = has_type_member<A>::value;
auto b = has_type_member<B>::value;
Para probar la implementación por tipos de flotador y los siguientes tipos:
Probado por implementación estándar
#include <type_traits>
// primary template handles types that have no nested ::type member:
template< class, class = int >
struct has_type_member : std::false_type { };
// specialization recognizes types that do have a nested ::type member:
template< class T >
struct has_type_member<T, std::void_t<typename T::type>> : std::true_type { };
De
bool f = false;
bool a = true;
bool b = true;
bool x = has_type_member<A, int>::value; //x = false;
esta referencia std::void_t
#include <type_traits>
// primary template handles types that have no nested ::type member:
template< class, class = int >
struct has_type_member : std::false_type { };
template< class T >
struct has_type_member<T, void> : std::true_type { };
// specialization recognizes types that do have a nested ::type member:
template< class T >
struct has_type_member<T, std::void_t<typename T::type>> : std::true_type { };
Producción
/home/insights/insights.cpp:14:8: error: redefinition of 'has_type_member<T, std::void_t<typename T::type>>'
struct has_type_member<T, std::void_t<typename T::type>> : std::true_type { };
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/insights/insights.cpp:8:8: note: previous definition is here
struct has_type_member<T, void>: std::true_type {};
^
1 error generated.
Error while processing /home/insights/insights.cpp.
caso 1 has_type_member<T, std::void_t<typename T::type>>
Producción has_type_member
Entonces, has_type_member<T, void>
definió una especialización de
y la firma es exactamente
#include <type_traits>
template< class, class = void >
struct has_type_member : std::false_type { };
// specialize 2nd type as void
template< class T>
struct has_type_member<T, void> : std::true_type { };
.
bool f = true;
bool a = true;
bool b = true;
caso 2
- Producción:
has_type_member<float>
- Este caso muestra que el compilador:
has_type_member<float, void>
- Quería encontrar una pareja para
std::true_type
Descubrí que la plantilla requiere 2 argumentos, luego llené el segundo argumento con los argumentos predeterminados. La estructura era como
#include <type_traits>
template< class, class = void >
struct has_type_member : std::false_type { };
template<class T>
struct has_type_member<T, typename T::type>: std::true_type {};
Encontré una especialización de esta firma y obtuve el valor de
bool f = false;
bool a = false;
bool b = true;
caso 3
has_type_member<float>
Producción:has_type_member<float, void>
caso f- se completó en
typename float::type
. - Entonces el compilador intentó
y falló
has_type_member<A>
La plantilla principal elegida.has_type_member<A, void>
- caso un
has_type_member<A, typename A::type>
se completó enhas_type_member<A, int>
- Entonces el compilador intentó
has_type_member<A, void>
- y descubrí que era
El compilador decidió que no era una especialización de
has_type_member<B>
Luego se eligió la plantilla principal.has_type_member<B, void>
caso b- se completó en
has_type_member<B, typename B::type>
.has_type_member<B, void>
Entonces el compilador intentó - y descubrí que era
has_type_member<B, void>
true_type
.
El compilador decidió que era una especialización de
#include <type_traits>
//int as default 2nd argument
template< class, class = int >
struct has_type_member : std::false_type { };
template<class T>
struct has_type_member<T, std::void<typename T::type>>: std::true_type {};
escogido.
bool f = false;
bool a = false;
bool b = false;
caso 4 has_type_member<T>
Producción: has_type_member<T, int>
El true_type
es de tipo has_type_member<T, void>
para las 3 variables, mientras que el
tiene firma como
si es valido std::void_t
Conclusión
- Entonces el
T::type
: - Comprobar si
-
válido.
std::void
Proporciona una especialización de la plantilla principal si solo se proporciona un argumento de plantilla.qué es
?
Anuncio 2) Imagina que la afirmación estática se escribió como:
has_member<A,int>::value
. Entonces, la especialización parcial que evalúa ahas_member<A,void>
no puede coincidir. Por lo tanto, necesita serhas_member<A,void>::value
o, con azúcar sintáctico, un argumento predeterminado de tipovoid
.– mojar
29 de diciembre de 2014 a las 10:57
@dyp Gracias, lo editaré. Mh, no veo la necesidad de tener
has_member< T , class = void >
incumplir envoid
todavía. 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?– tonterías
29 de diciembre de 2014 a las 11:29
Tenga en cuenta que en esta propuesta, open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4436.pdfWalter cambió
template <class, class = void>
atemplate <class, class = void_t<>>
. Así que ahora somos libres de hacer lo que queramos convoid_t
implementación de plantilla de alias 🙂– MaxPlancton
8 de marzo de 2018 a las 17:33