¿Por qué no puedo crear un vector de lambdas (del mismo tipo) en C ++ 11?

88

Estaba tratando de crear un vector de lambda, pero fallé:

auto ignore = [&]() { return 10; };  //1
std::vector<decltype(ignore)> v;     //2
v.push_back([&]() { return 100; });  //3

Hasta la línea 2, se compila bien . Pero la línea # 3 da error de compilación :

error: no hay función coincidente para llamar a 'std :: vector <main () :: <lambda () >> :: push_back (main () :: <lambda ()>)'

No quiero un vector de punteros de función o un vector de objetos de función. Sin embargo, el vector de objetos de función que encapsulan expresiones lambda reales funcionaría para mí. es posible?

Nawaz
fuente
23
"No quiero un vector de punteros de función o un vector de objetos de función". Pero eso es lo que pediste. Una lambda es un objeto de función.
Nicol Bolas

Respuestas:

135

Cada lambda tiene un tipo diferente, incluso si tienen la misma firma. Debe usar un contenedor de encapsulación en tiempo de ejecución, por ejemplo, std::functionsi desea hacer algo así.

p.ej:

std::vector<std::function<int()>> functors;
functors.push_back([&] { return 100; });
functors.push_back([&] { return  10; });
Perrito
fuente
52
Dirigir un equipo de desarrolladores de cien personas me suena más a una pesadilla :)
Jeremy Friesner
10
Además, no olvide que las lambdas sin captura (estilo []) se pueden degradar en punteros de función. De modo que podría almacenar una matriz de punteros de función del mismo tipo. Tenga en cuenta que VC10 aún no lo implementa.
Nicol Bolas
Por cierto, ¿no debería usarse capture-less en esos ejemplos de todos modos? ¿O es necesario? - Por cierto, lambda sin captura al puntero de función parece ser compatible con VC11. Aunque no lo probé.
Klaim
2
¿Es posible crear un vector que almacene funciones de diferente tipo? es decir, en lugar de limitarlo a std::function<int(), ¿podría usar diferentes prototipos de funciones?
manatttta
2
@manatttta ¿Cuál sería el punto? Los contenedores existen para almacenar objetos del mismo tipo, para organizarlos y manipularlos juntos. También podría preguntar '¿puedo crear un vectoralmacenamiento tanto std::functiony std::string?' Y la respuesta es la misma: no, porque ese no es el uso previsto. Puede usar una clase de estilo 'variante' para realizar un borrado de tipo suficiente para poner cosas dispares en un contenedor, al tiempo que incluye un método para que un usuario determine el tipo 'real' y, por lo tanto, elija qué hacer con (por ejemplo, cómo llamar) cada elemento ... pero de nuevo, ¿por qué ir tan lejos? ¿Existe alguna justificación real?
underscore_d
40

Todas las expresiones lambda tienen un tipo diferente, incluso si son idénticas carácter por carácter . Estás empujando una lambda de un tipo diferente (porque es otra expresión) en el vector, y eso obviamente no funcionará.

Una solución es hacer un vector de en su std::function<int()>lugar.

auto ignore = [&]() { return 10; };
std::vector<std::function<int()>> v;
v.push_back(ignore);
v.push_back([&]() { return 100; });

En otra nota, no es una buena idea usarlo [&]cuando no está capturando nada.

R. Martinho Fernandes
fuente
14
No necesita ()lambdas que no tengan argumentos.
Puppy
18

Si bien lo que otros han dicho es relevante, aún es posible declarar y usar un vector de lambda, aunque no es muy útil:

auto lambda = [] { return 10; };
std::vector<decltype(lambda)> vec;
vec.push_back(lambda);

Por lo tanto, puede almacenar cualquier cantidad de lambdas allí, ¡siempre que sea una copia / movimiento de lambda!

Luc Danton
fuente
Lo que en realidad podría ser útil si el retroceso ocurre en un bucle con diferentes parámetros. Presumiblemente con fines de evaluación perezosa.
MaHuJa
7
No, no pones los parámetros en el vector, solo el objeto de función .. Por lo tanto, sería un vector con todas las copias de la misma lambda
hariseldon78
16

Si su lambda no tiene estado, es decir, [](...){...}C ++ 11 permite que se degrade en un puntero de función. En teoría, un compilador compatible con C ++ 11 podría compilar esto:

auto ignore = []() { return 10; };  //1 note misssing & in []!
std::vector<int (*)()> v;     //2
v.push_back([]() { return 100; });  //3
MSN
fuente
4
Para que conste auto ignore = *[] { return 10; };haría ignoreun int(*)().
Luc Danton
1
@Luc, ¡eso es asqueroso! ¿Cuándo agregaron eso?
MSN
3
Bueno, dado que la función de conversión que permite tomar un puntero de función en primer lugar tiene la obligación de no serlo explicit, eliminar la referencia a una expresión lambda es válido y eliminar la referencia del puntero resultante de la conversión. Luego, el uso de autoesa referencia vuelve a convertirse en un puntero. (Usando auto&o auto&&habría mantenido la referencia.)
Luc Danton
Ah ... Desreferenciar el puntero resultante. Eso tiene sentido. ¿La falta fue ()intencional o accidental?
MSN
Intencional, la expresión lambda es equivalente (pero dos caracteres más corta).
Luc Danton
6

Puede usar una función de generación de lambda (actualizada con la corrección sugerida por Nawaz):

#include <vector>
#include <iostream>

int main() {
    auto lambda_gen = [] (int i) {return [i](int x){ return i*x;};} ;

    using my_lambda = decltype(lambda_gen(1));

    std::vector<my_lambda> vec;

    for(int i = 0; i < 10; i++) vec.push_back(lambda_gen(i));

    int i = 0;

    for (auto& lambda : vec){
        std::cout << lambda(i) << std::endl;
        i++;
    }
}

Pero creo que básicamente hiciste tu propia clase en este momento. De lo contrario, si las lambdas tienen caputres / args completamente diferentes, etc., probablemente tenga que usar una tupla.

antediluviano
fuente
Buena idea envolverlo en una función como la lambda_genque a su vez puede ser una lambda. Sin embargo, auto a = lambda_gen(1);realiza una llamada innecesaria, que se puede evitar si escribimos esto decltype(lambda_gen(1)).
Nawaz
Sin embargo, ¿eso no supone una llamada extra? También otro punto menor es que la pregunta establece C ++ 11, por lo que creo que sería necesario agregar un tipo de retorno final a la función.
antediluviano
No. Todo lo que hay adentro no decltype está evaluado , por lo que la llamada no se realiza realmente. Es el mismo caso con sizeoftambién. Además, este código no funcionará en C ++ 11 incluso si agrega un tipo de retorno final.
Nawaz
4

Cada lambda es de un tipo diferente. Debe utilizar en std::tuplelugar de std::vector.

Paul Fultz II
fuente