c++ - puede - lambda sintaxis
¿Cuál es el tipo de retorno de una expresión lambda si se devuelve un elemento de un vector? (3)
Considere el siguiente fragmento:
#include <iostream>
#include <vector>
#include <functional>
int main()
{
std::vector<int>v = {0,1,2,3,4,5,6};
std::function<const int&(int)> f = [&v](int i) { return v[i];};
std::function<const int&(int)> g = [&v](int i) -> const int& { return v[i];};
std::cout << f(3) << '' '' << g(3) << std::endl;
return 0;
}
Esperaba el mismo resultado: en f
, v
se pasa por referencia constante, por lo que v[i]
debería tener const int&
tipo.
Sin embargo, obtengo el resultado
0 3
Si no uso std :: function, todo está bien:
#include <iostream>
#include <vector>
#include <functional>
int main()
{
std::vector<int>v = {0,1,2,3,4,5,6};
auto f = [&v](int i) { return v[i];};
auto g = [&v](int i) -> const int& { return v[i];};
std::cout << f(3) << '' '' << g(3) << std::endl;
return 0;
}
salida:
3 3
Por lo tanto, me pregunto:
En el segundo fragmento, ¿cuál es el tipo de retorno de la expresión lambda
f
? ¿Esf
lo mismo queg
?En el primer fragmento, ¿qué pasó cuando se construyó la
std::function f
, causando el error?
¿Cuál es el tipo de retorno de una expresión lambda si se devuelve un elemento de un vector?
Esa es la pregunta incorrecta.
Debería preguntarse ¿cuál es el tipo de devolución de una expresión lambda si no se especifica explícitamente? .
La respuesta se da en C ++ 11 5.1.2 [expr.prim.lambda] párrafo 5, donde dice si el lambda no tiene return expr;
declaración devuelve void
, de lo contrario:
el tipo de la expresión devuelta después de la conversión lvalue-to-rvalue (4.1), la conversión de matriz a puntero (4.2) y la conversión de función a puntero (4.3);
(Consulte el comentario de TC a continuación para DR 1048 que modificó esta regla ligeramente, y los compiladores realmente implementan la regla modificada, pero no importa en este caso).
La conversión lvalue-a-rvalue significa que si la instrucción return arroja un lvalue como v[i]
se descompone en un valor r, es decir, devuelve solo int
por valor.
Entonces, el problema con su código es que el lambda devuelve un valor temporal, pero la std::function<const int&(int)>
que lo envuelve vincula una referencia a ese temporal. Cuando intenta imprimir el valor, el temporal ya se ha ido (estaba vinculado a un objeto, un marco de pila que ya no existe), por lo que tiene un comportamiento indefinido.
La solución es usar std::function<int(int)>
o asegurarse de que la lambda devuelva una referencia válida, no un valor r.
En C ++ 14, los no lambdas también pueden usar la deducción del tipo de devolución, por ejemplo:
auto f(std::vector<int>& v, int i) { return v[i]; }
Esto sigue reglas similares (pero no idénticas) a las reglas de C ++ 11 para lambdas, por lo que el tipo de retorno es int
. Para devolver una referencia, debe usar:
decltype(auto) f(std::vector<int>& v, int i) { return v[i]; }
Supongo que la primera lambda podría devolver
int
oint&
¹. Notconst int&
como la referenciav
no es para un objeto const (no importa que la captura en sí no sea mutable, ya que esa es la referencia misma)Los tiempos de vida de los temporales se extienden hasta el final del alcance adjunto cuando están vinculados a un const-ref
¹ Intentaré profundizar en eso más tarde. Por ahora, voy con la intuición de deducciones regulares y digo que auto
eliminaría int
, mientras que auto&
deduciría int&
, así que espero que el tipo de retorno real sea int
. Si alguien me gana, mejor. No tendré tiempo por unas pocas horas
Vea la prueba proporcionada por @milleniumbug: Live On Coliru
El tipo de devolución de una lambda utiliza las reglas de deducción del tipo de devolución auto
, que quita la referencia. (Originalmente usaba un conjunto ligeramente diferente de reglas basado en la conversión lvalue-to-rvalue (que también eliminaba la referencia), pero eso fue cambiado por un DR ).
Por lo tanto, [&v](int i) { return v[i];};
devuelve int
. Como resultado, en std::function<const int&(int)> f = [&v](int i) { return v[i];};
, al llamar a f()
devuelve una referencia colgante. Al vincular una referencia a un temporal, se extiende la vida útil del temporal, pero en este caso el enlace ocurrió en el interior de la maquinaria de std::function
, de modo que cuando f()
regresa, el temporal ya no está.
g(3)
está bien porque el const int &
returned está ligado directamente al vector elemento v[i]
, por lo que la referencia nunca está colgando.