c++ - kali - Usando el argumento de la función como parte de una expresión constante-gcc vs clang
where is clang installed (3)
Considere el siguiente fragmento de código:
template <bool> struct B { };
template <typename T>
constexpr bool pred(T t) { return true; }
template <typename T>
auto f(T t) -> decltype(B<pred(t)>{})
{
}
clang ++ (tronco) compila el código
g ++ (troncal) falla en la compilación con el siguiente error:
src:7:34: error: template argument 1 is invalid auto f(T t) -> decltype(B<pred(t)>{}) ^ src:7:34: error: template argument 1 is invalid src:7:34: error: template argument 1 is invalid src:7:34: error: template argument 1 is invalid src:7:34: error: template argument 1 is invalid src:7:34: error: template argument 1 is invalid src:7:25: error: invalid template-id auto f(T t) -> decltype(B<pred(t)>{}) ^ src:7:36: error: class template argument deduction failed: auto f(T t) -> decltype(B<pred(t)>{}) ^ src:7:36: error: no matching function for call to ''B()'' src:1:24: note: candidate: ''template<bool <anonymous> > B()-> B<<anonymous> >'' template <bool> struct B { }; ^ src:1:24: note: template argument deduction/substitution failed: src:7:36: note: couldn''t deduce template parameter ''<anonymous>'' auto f(T t) -> decltype(B<pred(t)>{}) ^
A pesar de que el diagnóstico de g ++ es engañoso, asumo que el problema aquí es que t
no es una expresión constante . Cambiando el código a ...
decltype(B<pred(T{})>{})
... corrige el error de compilación en g ++: ejemplo en vivo en godbolt.org
¿Qué compilador se está comportando correctamente aquí?
El compilador está esperando un parámetro en ese contexto porque necesita evaluar el tipo de función completa (plantilla sobrecargada). Dada la implementación de pred, cualquier valor funcionaría en esa ubicación. Aquí está vinculando el tipo de plantilla del parámetro f al argumento. El compilador g ++ parece estar asumiendo de manera simplificada que constexpr
función de constexpr
plantilla será alterada de alguna manera por cualquier parámetro a menos que también sean const
, lo que, como se ha demostrado, y clang está de acuerdo, no es necesariamente el caso.
Todo se reduce a la profundidad de la implementación de la función en que el compilador marca la función como no constante debido a la contribución no constante al valor de retorno.
Luego está la cuestión de si la función está instanciada y requiere que el compilador compile realmente el código en lugar de realizar el análisis de la plantilla que, al menos con g ++, parece ser un nivel diferente de compilación.
Luego fui a la norma y permitieron amablemente que el compilador del creador hiciera exactamente esa suposición simplificadora, y la creación de instancias de la función de plantilla solo debería funcionar para f<const T>
o f <const T&>
Las funciones constexpr` deben tener: cada uno de sus parámetros debe ser LiteralType
Por lo tanto, el código de la plantilla debe compilarse pero fallar si se crea una instancia con un T. no constante.
t
no es un valor constexpr, esta media pred(t)
tampoco es constexpr. No puede usarlo en B<pred(t)>
porque esto necesita constexpr.
Esta versión se compila correctamente:
template <bool> struct B { };
template <typename T>
constexpr bool pred(T t) { return true; }
template <typename T, T t>
auto f() -> decltype(B<pred(t)>{})
{
}
Otro código válido es:
template <typename T>
auto f(T t) -> decltype(pred(t))
{
}
Esto se debe a que no evalúa pred(t)
solo obtiene información de tipo. B<pred(t)>
necesita una evaluación de pred(t)
contrario obtendrá B<true>
o B<false>
, para cualquier valor normal que no pueda.
std::integral_constant<int, 0>{}
puede funcionar en el caso de Clang es probablemente porque su valor se genera como parte de un tipo y siempre es el mismo. Si cambiamos un poco el código:
template <typename T>
auto f(T t) -> decltype(B<pred(decltype(t){})>{})
{
return {};
}
Clang y GCC lo compilan, en caso de que std::integral_constant
, tanto t
como decltype(t){}
tengan siempre el mismo valor.
GCC está equivocado No hay ninguna regla que impida usar los parámetros de una función en una expresión constante de esta manera.
Sin embargo, no puede usar el valor del parámetro en dicho contexto, y el conjunto de tipos T
para los que f
es reclamable es bastante restringido. Para ver por qué, debemos considerar qué construcciones se evaluarán al evaluar la expresión pred(t)
:
// parameters renamed for clarity
template <typename U>
constexpr bool pred(U u) { return true; }
template <typename T>
auto f(T t) -> decltype(B<pred(t)>{});
La semántica de evaluación para la llamada pred(t)
es la siguiente:
- copiar-inicializar el parámetro
u
delpred
desde el parámetrot
def
- evaluar el cuerpo de
pred
, que crea trivialmente un valorbool
true
- destruirte
Por lo tanto, f
solo se puede llamar a los tipos T
para los cuales lo anterior solo implica construcciones que son válidas durante la evaluación constante (consulte [expr.const]p2 para ver las reglas). Los requisitos son:
-
T
debe ser un tipo literal - la inicialización de la copia de
u
desdet
debe ser una expresión constante, y en particular, no debe realizar una conversión de valor a rvalor en ningún miembro det
(porque sus valores no se conocen), y no debe nombrar a ningún miembro de referencia det
En la práctica, esto significa que f
es llamable si T
es un tipo de clase vacío con un constructor de copia predeterminado, o si T
es un tipo de clase cuyo constructor de copia es constexpr
y no lee ningún miembro de su argumento, o (extrañamente) si T
es std::nullptr_t
(aunque clang actualmente tiene el caso nullptr_t
incorrecto ).