installed - GCC y Clang no están de acuerdo con las capturas de C++ 17 constexpr lambda
where is clang installed (1)
Ambas implementaciones tienen errores, pero me inclino a pensar que GCC obtuvo la respuesta correcta aquí.
Al descartar la captura de i
, Clang se niega a compilar el código. Eso significa que claramente tiene un error en alguna parte.
Una expresión
e
es una expresión constante central, a menos que la evaluación dee
, siguiendo las reglas de la máquina abstracta, evalúe una de las siguientes expresiones:
- [...]
- en una expresión lambda , una referencia a una [...] variable con una duración de almacenamiento automático definida fuera de esa expresión lambda , donde la referencia sería un uso estándar;
- [...]
El comportamiento de Clang es esquizofrénico: si el uso de i
en el cuerpo no es un uso estándar, entonces no es necesario capturarlo, sin embargo, rechaza el código en el OP si se elimina la captura explícita; OTOH, si se trata de un uso estándar, entonces, mediante el unwrap(i)
anterior unwrap(i)
no es una expresión constante, por lo que debe rechazar la inicialización de x
.
La implementación lambda de GCC es lamentablemente mala con respecto al uso de odr. Hace un plegado constante muy temprano, lo que resulta en todo tipo de travesuras sutiles. Por otro lado, para las capturas explícitas, transforma todos los usos, ya sea que se trate o no de un uso estándar. El plegado constante agresivo significa que acepta el código de OP si se elimina la captura de i
.
Suponiendo que unwrap(i)
no utiliza idr, entonces es correcto que, por [expr.const] /2.12, el código de OP esté mal formado.
¿ unwrap(i)
realidad odr-use i
? Esa pregunta se reduce a si la inicialización de copia del objeto de parámetro de unwrap
considera como la aplicación de una conversión de valor-a-valor a i
. No veo nada en el estándar que diga explícitamente que aquí se aplique una conversión de valor-a-valor, y en su lugar [dcl.init]/17.6.2 indica que llamamos constructor (en este caso, lo trivial se define implícitamente constructor de copia) pasando i
como el argumento vinculado a su parámetro, y el enlace de referencia es un ejemplo clásico de uso de odr.
Para estar seguro, la aplicación de una conversión de I a R resultaría en una inicialización de copia de un objeto integral_constant<int, 42>
desde i
, pero el problema aquí es que nada en el estándar dice lo contrario, que todas las inicializaciones de copia de un objeto integral_constant<int, 42>
de i
cuenta como conversiones de 1 a 2.
Considere este ejemplo que declara una variable como constexpr, la captura mediante una copia en un lambda y declara otra variable constexpr que es el resultado de una función constexpr que desenvuelve un parámetro de plantilla que no es de tipo de la variable original.
#include <utility>
template<int I>
constexpr auto unwrap(std::integral_constant<int, I>) {
return I;
}
int main() {
constexpr auto i = std::integral_constant<int, 42>{};
constexpr auto l = [i]() {
constexpr int x = unwrap(i);
};
}
Clang (tronco) acepta este código. ( wandbox )
GCC (troncal) falla con el siguiente mensaje de error ( wandbox ):
lambda_capture.cpp:11:31: error: the value of ‘i’ is not usable in a constant expression
constexpr int x = unwrap(i);
^
lambda_capture.cpp:10:28: note: ‘i’ was not declared ‘constexpr’
constexpr auto l = [i]() {
¿Qué compilador es correcto? Me parece que este es un error de GCC, donde la constancia de las capturas lambda no se propaga correctamente al contexto lambda.