c++ - ¿Por qué[=]{} tiene una captura lambda?
c++11 language-lawyer (2)
El cambio que permitió la conversión fue iniciado por un comentario del organismo nacional. Consulte n3052: Conversión de Lambdas a punteros de función, que hace referencia al comentario del organismo nacional UK 42 :
Un lambda con una lista de captura vacía tiene una semántica idéntica a un tipo de función normal. Al requerir esta asignación, obtenemos un tipo lambda eficiente con una API conocida que también es compatible con el sistema operativo existente y las funciones de la biblioteca de C.
y la resolución de N3052
fue:
Resolución: agregue un nuevo párrafo: "Una expresión lambda con un conjunto de captura vacío se convertirá en puntero al tipo de función R (P), donde R es el tipo de retorno y P es la lista de tipos de parámetros de la expresión lambda". Además, podría ser bueno (a) permitir la conversión a la referencia de la función y (b) permitir los tipos de puntero de la función "C" externa.
...
Agregue un nuevo párrafo después del párrafo 5. El objetivo de esta edición es obtener una conversión de puntero de cierre a función para un lambda sin captura de lambda.
El tipo de cierre para una expresión lambda sin captura lambda tiene una función de conversión const no explícita no virtual pública para apuntar a la función que tiene los mismos parámetros y tipos de retorno que el operador de llamada a función del tipo de cierre. El valor devuelto por esta función de conversión será la dirección de una función que, cuando se invoca, tiene el mismo efecto que invocar al operador de llamada a función del tipo de cierre.
Y ahí es donde estamos hoy. Tenga en cuenta que el comentario dice que la lista de captura vacía y lo que tenemos hoy parece coincidir con la intención tal como está redactada en el comentario.
Parece que fue una solución basada en un comentario de un organismo nacional y se aplicó por poco.
En un nivel intuitivo, tiene sentido que un lambda que no necesita llevar un estado (a través de una referencia o de otra manera) debe ser fácilmente convertible en un puntero de función desnudo. Sin embargo, recientemente me sorprendió ver el siguiente error en GCC, Clang y MSVC:
int main(int, char *[]) {
void (*fp)() = []{}; // OK
//fp = [=]{}; // XXX - no user defined conversion operator available
//fp = [&]{}; // XXX - same ...
}
La especificación de C ++ 17 (o al menos la versión borrador pública N4713 visible ), se refiere en el punto 7 del § 8.4.5.1 [expr.prim.lambda.closure] a las lambdas con y sin capturas:
El tipo de cierre para una expresión lambda no genérica sin captura lambda cuyas restricciones (si las hubiera) se cumplen tiene una función de conversión a puntero para funcionar con el enlace en lenguaje C ++ (10.5) que tiene los mismos parámetros y tipos de retorno que los tipos de cierre función de operador de llamada. ...
Sin embargo, al analizar la gramática formal, puede ver lo siguiente en § 8.4.5 [expr.prim.lambda] :
- lambda-expresión :
- lambda-introducer compuesto-declaración
- ...
- lambda-introductor
- [ lambda-captura opt ]
- ...
y en § 8.4.5.2 [expr.prim.lambda.capture] :
- lambda-captura :
- captura por defecto
- lista de captura
- captura por defecto, lista de captura
- captura por defecto :
- Y
- =
Así que todos los compiladores obedecían la letra de la ley para mi consternación ...
¿Por qué el lenguaje define la existencia de una captura como una distinción gramatical estrecha en la declaración en lugar de basarla en si el cuerpo contiene referencias a algún estado no estático / capturado?
La regla que propones sería extremadamente frágil, especialmente en el mundo pre-P0588R1 cuando las capturas implícitas dependían del uso de ODR.
Considerar:
void g(int);
void h(const int&);
int my_min(int, int);
void f(int i) {
const int x = 1, y = i;
[=]{ g(x); }; // no capture, can convert?
[=]{ g(y); }; // captures y
[=]{ h(x); }; // captures x
[=]{ my_min(x, 0); }; // no capture, can convert?
[=]{ std::min(x, 0); }; // captures x
}