Función que devuelve una expresión lambda

88

Me pregunto si es posible escribir una función que devuelva una función lambda en C ++ 11. Por supuesto, un problema es cómo declarar dicha función. Cada lambda tiene un tipo, pero ese tipo no se puede expresar en C ++. No creo que esto funcione:

auto retFun() -> decltype ([](int x) -> int)
{
    return [](int x) { return x; }
}

Ni esto:

int(int) retFun();

No tengo conocimiento de ninguna conversión automática de lambdas a, digamos, punteros a funciones, o algo así. ¿La única solución es hacer a mano un objeto de función y devolverlo?

Bartosz Milewski
fuente
1
Para agregar lo que ya se ha dicho, las funciones lambda sin estado se pueden convertir en punteros de función.
snk_kid
3
En mi opinión, su primera opción no funcionará ya que la lambda en el decltypeno es la misma que en el cuerpo de la función y, por lo tanto, tiene un tipo diferente (incluso si incluyó la declaración de retorno)
Motti
1
Por cierto, si una lambda tiene una cláusula de captura vacía, puede convertirse implícitamente en un puntero para funcionar.
GManNickG
@GMan: A menos que esté usando Visual C ++ 2010 o una versión de g ++ lanzada hace más de un año (o más o menos). La conversión implícita de lambda sin captura a puntero de función no se agregó hasta marzo de 2010 en N3092.
James McNellis
4
Las expresiones lambda en general no pueden aparecer en operandos no evaluados. Entonces decltype([](){})o sizeof([]() {})está mal formado, no importa dónde lo escriba.
Johannes Schaub - litb

Respuestas:

98

No necesita un objeto de función hecho a mano, solo use std::function, al que las funciones lambda son convertibles:

Este ejemplo devuelve la función de identidad entera:

std::function<int (int)> retFun() {
    return [](int x) { return x; };
}
Sean
fuente
6
¡Duh! Me encanta StackOverflow. Me habría llevado mucho más tiempo investigar el tema y luego obtener la respuesta en StackOverflow. ¡Gracias Sean!
Bartosz Milewski
6
Eso va a causar una asignación de memoria en el constructor de std::function.
Maxim Egorushkin
2
@Maxim Yegorushkin std :: función tiene semántica de movimiento y además puede usar asignadores personalizados y el borrador de trabajo C ++ 0x tiene estas notas: "[Nota: se recomienda que las implementaciones eviten el uso de memoria asignada dinámicamente para pequeños objetos llamables, por ejemplo , donde el objetivo de f es un objeto que contiene solo un puntero o referencia a un objeto y un puntero de función miembro. —nota final] ", por lo que básicamente no puede hacer muchas suposiciones sobre qué estrategia de asignación está usando una implementación en particular, pero debería poder para usar sus propios asignadores (agrupados) de todos modos.
snk_kid
1
@Maxim: Mi respuesta fue a la pregunta "¿La única solución es crear manualmente un objeto de función y devolverlo?"
Sean
2
Solo tenga en cuenta que std::functionemplea el borrado de tipos, lo que puede significar el costo de realizar una llamada de función virtual al llamar a std::function. Algo a tener en cuenta si la función devuelta se utilizará en un bucle interno estrecho u otro contexto en el que la leve ineficiencia importa.
Anthony Hall
29

Para este simple ejemplo, no es necesario std::function.

Del estándar §5.1.2 / 6:

El tipo de cierre para un lambda-expresión sin lambda de captura tiene una función pública no virtual no explícita conversión a const puntero a la función que tienen los mismos parámetros y valores de retorno como operador de llamada de 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 la invocación del operador de llamada de función del tipo de cierre.

Debido a que su función no tiene una captura, significa que la lambda se puede convertir en un puntero a una función de tipo int (*)(int):

typedef int (*identity_t)(int); // works with gcc
identity_t retFun() { 
  return [](int x) { return x; };
}

Ese es mi entendimiento, corrígeme si me equivoco.

usuario534498
fuente
1
Esto suena bien. Desafortunadamente, no funciona con el compilador actual que estoy usando: VS 2010. La conversión std :: función funciona.
Bartosz Milewski
3
Sí, la redacción final de esta regla llegó demasiado tarde para VC2010.
Ben Voigt
He agregado un ejemplo de código. Aquí tienes un programa completo .
jfs
@JFSebastian - ¿Cuál es la vida útil de la lambda en este ejemplo? ¿Es lo suficientemente largo para sobrevivir al resultado de la conversión a puntero de función?
Flexo
1
@JFSebastian - Parece que no puedo encontrar una respuesta a eso, así que lo hice como una pregunta por derecho propio: stackoverflow.com/questions/8026170/…
Flexo
21

Puede devolver la función lambda desde otra función lambda, ya que no debe especificar explícitamente el tipo de retorno de la función lambda. Simplemente escriba algo así en el alcance global:

 auto retFun = []() {
     return [](int x) {return x;};
 };
Dzhioev
fuente
2
Eso solo es cierto cuando la lambda externa consiste solo en la declaración de retorno. De lo contrario, debe especificar el tipo de devolución.
Bartosz Milewski
3
Esta es la mejor respuesta, ya que no requiere polimorfismo en tiempo de ejecución de std :: function y permite que lambda tenga una lista de captura no vacía, sin embargo, usaría const auto fun = ...
robson3.14
2
@BartoszMilewski no es cierto desde C ++ 14.
dzhioev
19

Aunque la pregunta se refiere específicamente a C ++ 11, por el bien de otros que se topan con esto y tienen acceso a un compilador de C ++ 14, C ++ 14 ahora permite tipos de retorno deducidos para funciones ordinarias. Por lo tanto, el ejemplo de la pregunta se puede ajustar para que funcione como se desee simplemente soltando la -> decltypecláusula ... después de la lista de parámetros de la función:

auto retFun()
{
    return [](int x) { return x; }
}

Sin embargo, tenga en cuenta que esto no funcionará si return <lambda>;aparece más de uno en la función. Esto se debe a que una restricción en la deducción del tipo de retorno es que todas las declaraciones de retorno deben devolver expresiones del mismo tipo, pero el compilador le da a cada objeto lambda su propio tipo único, por lo que las return <lambda>;expresiones tendrán un tipo diferente.

Anthony Hall
fuente
4
¿Por qué mencionar los tipos deducidos de c ++ 14 pero omitir las lambdas polimórficas? auto retFun() { return [](auto const& x) { return x; }; }
sehe
1

Deberías escribir así:

auto returnFunction = [](int x){
    return [&x](){
        return x;
    }();
};

para obtener su devolución como función, y utilícela como:

int val = returnFunction(someNumber);
Tara de Terens
fuente
0

Si no tiene c ++ 11 y está ejecutando su código c ++ en microcontroladores, por ejemplo. Puede devolver un puntero vacío y luego realizar un molde.

void* functionThatReturnsLambda()
{
    void(*someMethod)();

    // your lambda
    someMethod = []() {

        // code of lambda

    };

    return someMethod;
}


int main(int argc, char* argv[])
{

    void* myLambdaRaw = functionThatReturnsLambda();

    // cast it
    auto myLambda = (void(*)())myLambdaRaw;

    // execute lambda
    myLambda();
}
Tono Nam
fuente