c++ - ¿Puede una plantilla de alias de identidad ser una referencia de reenvío?
templates c++14 (2)
Considere este código:
template<class T> using identity = T;
template<class T> void foo(identity<T>&&) { } //#1
template<class T> void foo(T&&) { } //#2
int main()
{
int i{};
foo(i);
}
Tanto GCC como Clang lo rechazan porque el #2
es una redefinición del #1
. Si en realidad son la misma plantilla, podríamos esperar que el #1
comporte de la misma manera que el #2
, lo que significa que la identity<T>&&
debe actuar como una referencia de reenvío. Siguiendo esta lógica, no sabemos cuál es el correcto, pero GCC es al menos consistente.
Esto también es consistente con un ejemplo muy similar en la norma en [14.5.7p2].
También debemos considerar la forma en que la deducción de argumentos de la plantilla puede funcionar en este caso. Si la identity
fuera una plantilla de clase, su forma podría compararse con el tipo del argumento de la función sin mirar su definición, permitiendo que el compilador deduzca el argumento de la plantilla para T
Sin embargo, aquí tenemos una plantilla de alias; No se puede deducir T
a int
o int&
ninguna otra cosa a menos que la identity<T>
se reemplace por T
De lo contrario, ¿contra qué estamos emparejando? Una vez que se realiza la sustitución, el parámetro de función se convierte en una referencia de reenvío.
Todo lo anterior respalda la idea de identity<T>&&
(e identity<T&&>
) tratada como equivalente a una referencia de reenvío.
Sin embargo, parece que hay más en esto que la sustitución inmediata de la alias template-id con la correspondiente type-id. El párrafo [14.5.7p3] dice:
Sin embargo, si la plantilla-id es dependiente, la sustitución subsiguiente del argumento de la plantilla todavía se aplica a la plantilla-id. [Ejemplo:
template<typename...> using void_t = void; template<typename T> void_t<typename T::foo> f(); f<int>(); // error, int does not have a nested type foo
—En ejemplo]
Puede parecer que esto no tiene mucho que ver con su ejemplo, pero en realidad indica que la forma inicial de la plantilla-id todavía se tiene en cuenta en algunos casos, independientemente de la id-tipo sustituida. Supongo que esto abre la posibilidad de que la identity<T>&&
realidad no se pueda tratar como una referencia de reenvío después de todo.
Esta área parece estar subespecificada en el estándar. Esto se muestra en la cantidad de problemas abiertos que tratan con problemas similares, todos en la misma categoría en mi opinión: en qué casos se debe tener en cuenta la forma inicial de la plantilla-id en la creación de instancias, aunque se supone que debe ser reemplazada por la tipo-id correspondiente inmediatamente cuando se encuentra. Ver números 1980 , 2021 y 2025 . Incluso los problemas 1430 y 1554 podrían considerarse problemas similares.
En particular, el 1980 contiene el siguiente ejemplo:
template<typename T, typename U> using X = T;
template<typename T> X<void, typename T::type> f();
template<typename T> X<void, typename T::other> f();
con la nota:
CWG consideró que estas dos declaraciones no deberían ser equivalentes.
(CWG - el grupo de trabajo Core)
Una línea de razonamiento similar podría aplicarse a su ejemplo, haciendo que la identity<T>&&
no sea equivalente a una referencia de reenvío. Esto incluso podría tener un valor práctico, como una forma sencilla de evitar la avidez de una referencia de reenvío cuando todo lo que desea es una referencia de valor a una T. deducida.
Entonces, creo que has planteado un problema muy interesante. Puede que valga la pena agregar su ejemplo como nota para la 1980 , para asegurarse de que esto se tenga en cuenta al redactar la resolución.
En mi opinión, la respuesta a su pregunta es, por ahora, un rotundo "¿quién sabe?".
Actualización: En los comentarios a la otra question relacionada, Piotr S. señaló el problema 1700 , que se cerró como "no un defecto". Se refiere al caso muy similar descrito en esa pregunta, y contiene la siguiente razón:
Debido a que los tipos de parámetros de la función son los mismos, independientemente de que estén escritos directamente o mediante una plantilla de alias, la deducción debe manejarse de la misma manera en ambos casos.
Creo que se aplica igual de bien a los casos discutidos aquí, y resuelve el problema por ahora: todas estas formas deben tratarse como equivalentes a una referencia de reenvío.
(Será interesante ver si esto se modifica indirectamente por las resoluciones para las otras cuestiones abiertas, pero en su mayoría tratan con fallas de sustitución en lugar de deducciones por sí mismas, por lo que supongo que tal efecto indirecto es bastante improbable).
Todas las referencias estándar son al borrador de trabajo actual, N4431, el segundo borrador después del C ++ 14 final.
Tenga en cuenta que la cita de [14.5.7p3] es una adición reciente, incluida justo después de la versión final de C ++ 14 como la resolución de DR1558 . Creo que podemos esperar más adiciones en esta área ya que los otros problemas se resuelven de una manera u otra.
Hasta entonces, puede valer la pena hacer esta pregunta en la Norma ISO C ++ - Grupo de discusión ; Eso debería llamar la atención de las personas adecuadas.
Considere el siguiente fragmento de código a continuación:
template <class T>
using identity = T;
template <class T>
void foo(identity<T>&&) {}
int main()
{
int i{};
foo(i);
}
i
es un lvalue, por lo tanto, si foo
declara un parámetro de referencia de reenvío, debe compilarlo. Sin embargo, si la identity<T>&&
se convierte en int&&
, debería generar un error.
El código se compila en GCC 6.0.0 ( demo ).
El código no se compila en Clang 3.7.0 ( demo ) con un mensaje de error:
error: no known conversion from ''int''
to ''identity<int> &&'' (aka ''int &&'') for 1st argument
¿Cuál es la correcta?
No es una referencia de reenvío. C ++ 14 (n4140) 14.8.2.1/3 (énfasis mío):
... Si
P
es una referencia de valor a un parámetro de plantilla no calificado de CV y el argumento es un valor de l, se usa el tipo "referencia de valor de l aA
" en lugar deA
para la deducción de tipo.
Esta es la pieza de la norma que especifica cómo funcionan las referencias de reenvío. P
, el tipo del parámetro de función, es de tipo "rvalue reference to identity<T>
". identity<T>
es el tipo del parámetro de plantilla, pero no es un parámetro de plantilla en sí, por lo que no se aplica la regla de deducción de referencia de reenvío.
También podemos ver lo que 14.5.7 / 2 tiene que decir acerca de las plantillas de alias:
Cuando una plantilla-id se refiere a la especialización de una plantilla de alias, es equivalente al tipo asociado obtenido mediante la sustitución de sus argumentos de plantilla por los parámetros de plantilla en la ID de tipo de la plantilla de alias.
De modo que el alias sustituido es equivalente al tipo de T
, pero 14.8.2.1/3 lee "referencia a ... parámetro de plantilla", no "referencia a ... el tipo de parámetro de plantilla".