c++ - Es esto "si e es un paquete, entonces obtenga un nombre de plantilla, de lo contrario obtenga un nombre de variable" vĂ¡lido o no?
c++11 language-lawyer (2)
La expansión de un paquete de parámetros debería, o lo hace, hacer que un tipo de expresión dependa. Independientemente de si las cosas expandidas son de tipo dependiente.
Si no lo hiciera, habría un enorme agujero en las reglas de dependencia de tipo de C ++ y sería un defecto en el estándar.
Por lo tanto, A<void(type<decltype(t)>...)>::f
cuando t
es un paquete, independientemente de los trucos que tire de las partes void(
aquí )
para desempaquetar la t
, debe ser un tipo dependiente, y la template
se requiere antes de la f
si es una template
.
En el caso de que t
no sea un paquete, se pretende que el type<decltype(t)>
no sea dependiente (consulte open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1390 ), pero la norma puede o no estar de acuerdo en este punto (creo que no?)
Si los compiladores hicieron "lo que pretendía el comité", entonces cuando t
no es un paquete:
A<void(type<decltype(t)>...)>::f<0>(1)
puede significar
A<void(int...)>::f<0>(1)
cual es
A<void(int, ...)>::f<0>(1)
y si f
es una template
(su código lo convierte en un int
, pero creo que el intercambio de los dos debería funcionar) estaría bien. Pero el estándar aparentemente actualmente no está de acuerdo?
Entonces, si se implementó open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1390 , entonces podría intercambiar sus dos especializaciones A
La especialización void(T...,...)
debe tener una template<int> void f(int)
, y la especialización T
debe tener una static const int
.
Ahora, en el caso donde A<>
es dependiente (del tamaño de un paquete), ::f
es un int
y no necesita template
. En el caso en que A<>
no sea dependiente, ::f
es una template
pero no necesita desambiguación.
Podemos reemplazar el type<decltype(t)>...
con:
decltype(sizeof(decltype(t)*))...
y sizeof(decltype(t)*)
es de tipo no dependiente (es std::size_t
), decltype
nos da un std::size_t
, y ...
se trata como a una vieja escuela ...
arg. Esto significa que void(std::size_t...)
convierte en un tipo no dependiente, por lo que A<void(std::size_t...)>
no es dependiente, por lo que ::f
ser una plantilla no es una plantilla en una contexto dependiente.
En el caso donde t
es un paquete de parámetros con un elemento.
decltype(sizeof(decltype(t)*))...
se convierte en
std::size_t
pero en un contexto dependiente (una copia por elemento en el paquete t
). Así obtenemos
A<void(std::size_t)>::f
que se presume que es un valor escalar, por lo que
A<void(std::size_t)>::f<0>(1)
se convierte en una expresión que evalúa a false
.
(Cadena de lógica generada en una discusión con Johannes en comentarios en la pregunta original).
He intentado construir un caso que no requiere un nombre de tipo o una plantilla , pero sigo generando una variable o plantilla dependiendo de si un nombre dado t
es un paquete de parámetros de función o no
template<typename T> struct A { template<int> static void f(int) { } };
template<typename...T> struct A<void(T...,...)> { static const int f = 0; };
template<typename> using type = int;
template<typename T> void f(T t) { A<void(type<decltype(t)>...)>::f<0>(1); }
int main() {
f(1);
}
Lo anterior se referirá a la static const int
y hará una comparación. Lo siguiente solo ha cambiado T t
para ser un paquete y hace que f
refiera a una plantilla, pero a GCC tampoco le gusta
template<typename ...T> void f(T ...t) { A<void(type<decltype(t)>...)>::f<0>(1); }
int main() {
f(1, 2, 3);
}
GCC se queja por la primera
main.cpp:5:68: error: incomplete type ''A<void(type<decltype (t)>, ...)>'' used in nested name specifier
template<typename T> void f(T t) { A<void(type<decltype(t)>...)>::f<0>(1); }
Y para el segundo
main.cpp:5:74: error: invalid operands of types ''<unresolved overloaded function type>'' and ''int'' to binary ''operator<''
template<typename ...T> void f(T ...t) { A<void(type<decltype(t)>...)>::f<0>(1); }
Tengo multiples preguntas
- ¿Funciona el código anterior según el idioma o hay un error?
- Ya que Clang acepta ambas variantes pero GCC rechaza, quería preguntar qué compilador es correcto.
Si quito el cuerpo de la plantilla principal, entonces para el caso
f(1, 2, 3)
, Clang se queja.main.cpp:5:42: error: implicit instantiation of undefined template ''A<void (int)>''
Tenga en cuenta que dice
A<void (int) >
, mientras que yo esperaríaA<void (int, int, int)>
. ¿Cómo se produce este comportamiento? ¿Se trata de un error en mi código, es decir, está mal formado o es un error en Clang? Me parece recordar un informe de defectos sobre el orden de expansión frente a la plantilla de sustitución de alias, ¿es relevante y hace que mi código esté mal formado?
Tu segundo caso está mal formado; A<void(type<decltype(t)>...)>::f<0>(1)
debe ser
A<void(type<decltype(t)>...)>::template f<0>(1)
// ~~~~~~~~~
Para el primer caso, ambos compiladores se están comportando incorrectamente; esto se consideró lo suficientemente confuso que se planteó el CWG 1520 para consultar el comportamiento correcto; la conclusión fue que la expansión del paquete debe aplicarse antes de la sustitución de alias:
La última interpretación (una lista de especializaciones) es la interpretación correcta; un paquete de parámetros no se puede sustituir en nada, incluida una especialización de plantillas de alias. CWG sintió que esto es lo suficientemente claro en la redacción actual.
Esto recuerda al CWG 1558 (plantillas de alias y SFINAE), que se corrigió para C ++ 14, pero se espera que los compiladores de C ++ 11 reciban esto correctamente, por lo que es decepcionante que gcc y clang se equivoquen (aunque para ser justos, se comportan correctamente en casos más simples, incluido el ejemplo motivador en CWG 1520). Tenga en cuenta que MSVC tuvo un error similar hasta hace poco; Se fija en VS2015.
Su código (solo en el primer caso) es correcto; pero como solución alternativa, puede modificar su plantilla de alias para usar y descartar su parámetro de plantilla, corrigiendo su programa para ambos compiladores; por supuesto, eso significa que su vulnerabilidad de open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1390 dejará de ser válida:
template<typename T> using type = decltype(((int(*)(T*))(0))(0)); // int
Sin embargo, no creo que su truco CWG 1390 pueda funcionar como se presenta, ya que aunque la expansión-sustitución de type<decltype(t)>...
no depende de los tipos de t...
, depende de su número :
template<typename T> struct A { template<int> static void f(int) {} };
template<> struct A<void(int, int, int)> { static const int f = 0; };
Como señala Yakk, se puede hacer que funcione si intercambias la plantilla de función miembro y el miembro de datos, ya que un miembro de datos está bien en el contexto dependiente.