¿Cómo declarar una función que acepta una lambda?

82

Leí en Internet muchos tutoriales que explicaban cómo usar lambdas con la biblioteca estándar (como std::find), y todos eran muy interesantes, pero no pude encontrar ninguno que explicara cómo puedo usar una lambda para mis propias funciones.

Por ejemplo:

int main()
{
    int test = 5;
    LambdaTest([&](int a) { test += a; });

    return EXIT_SUCCESS;
}

¿Cómo debo declarar LambdaTest? ¿Cuál es el tipo de su primer argumento? Y luego, ¿cómo puedo llamar a la función anónima que le pasa, por ejemplo, "10" como argumento?

Thomas Bonini
fuente

Respuestas:

78

Dado que probablemente también desee aceptar punteros de función y objetos de función además de lambdas, probablemente desee usar plantillas para aceptar cualquier argumento con una extensión operator(). Esto es lo que hacen las funciones estándar como find. Se vería así:

template<typename Func>
void LambdaTest(Func f) {
    f(10);
}

Tenga en cuenta que esta definición no utiliza ninguna característica de c ++ 0x, por lo que es completamente compatible con versiones anteriores. Es solo la llamada a la función que usa expresiones lambda que son específicas de c ++ 0x.

sepp2k
fuente
2
En caso de errores, los mensajes de error serán difíciles de entender.
liori
13
Depende de si es el mejor. Este usa una plantilla y el otro no. Esto significa que la función ya no puede ser virtual y no se puede definir por separado en el archivo cpp. std::functiontambién es perfectamente capaz de tomar tipos de clases de objetos de función, aunque es un poco más lento al llamar. Pero esa diferencia es insignificante para la mayoría de las aplicaciones :)
Johannes Schaub - litb
2
"lo mejor" está en el ojo del espectador :-) esta respuesta usa un functorque es agradable, pero realmente no responde a la pregunta original (y lo que me llevó aquí) que era "cómo uso una lambda para mis propias funciones" . Además, las plantillas tienen su propio conjunto de problemas, que la respuesta std::functionno tiene.
Marco Massenzio
1
@Marco Esta respuesta no requiere que uses funciones, te permite usar lo que quieras, incluidas lambdas.
sepp2k
68

Si no desea crear una plantilla para todo, puede hacer lo siguiente:

void LambdaTest (const std::function <void (int)>& f)
{
    ...
}
doble
fuente
1
Esta sintaxis en realidad me permite guardar la variable de función para llamarla más tarde, ¿verdad? Por ejemplo, quería implementar una función que permitiera ejecutar consultas de bases de datos asincrónicas, donde la lambda actúa como una devolución de llamada. (Por supuesto que no podría acceder a los cierres por referencia)
Thomas Bonini
1
¿No es más idiomático pasar funciones por valor?
fredoverflow
1
@Andreas Bonini: Sí, si guarda en std::function(no es una referencia), hace una copia de f. Sin embargo, no estoy seguro de cómo lambda / closures manejan las referencias cuando el objeto al que se hace referencia sale del alcance, probablemente UB. @FredOverflow: Tengo entendido que std::functionno es un objeto trivial, especialmente cuando se envuelven lambdas. Probablemente sea mejor hacer referencia a él para evitar copias innecesarias.
doblep
Si su lambda captura la pila por valor, entonces sí, la lambda puede sobrevivir a esas variables y continuará guardando copias de ellas. Si captura por referencia, tendrá un problema.
Kate Gregory
1
Tendremos que copiarlo dentro de la función, por lo que no tiene sentido copiarlo a medida que se pasa
Casebash
9

Me gustaría contribuir con este ejemplo simple pero que se explica por sí mismo. Muestra cómo pasar "cosas invocables" (funciones, objetos de función y lambdas) a una función o un objeto.

// g++ -std=c++11 thisFile.cpp

#include <iostream>
#include <thread>

using namespace std;

// -----------------------------------------------------------------
class Box {
public:
  function<void(string)> theFunction; 
  bool funValid;

  Box () : funValid (false) { }

  void setFun (function<void(string)> f) {
    theFunction = f;
    funValid = true;
  }

  void callIt () {
    if ( ! funValid ) return;
    theFunction (" hello from Box ");
  }
}; // class

// -----------------------------------------------------------------
class FunClass {
public:
  string msg;
  FunClass (string m) :  msg (m) { }
  void operator() (string s) {
    cout << msg <<  s << endl; 
  }
};

// -----------------------------------------------------------------
void f (string s) {
  cout << s << endl;
} // ()

// -----------------------------------------------------------------
void call_it ( void (*pf) (string) ) {
  pf( "call_it: hello");
} // ()

// -----------------------------------------------------------------
void call_it1 ( function<void(string)> pf ) {
  pf( "call_it1: hello");
} // ()

// -----------------------------------------------------------------
int main() {

  int a = 1234;

  FunClass fc ( " christmas ");

  f("hello");

  call_it ( f );

  call_it1 ( f );

  // conversion ERROR: call_it ( [&] (string s) -> void { cout << s << a << endl; } );

  call_it1 ( [&] (string s) -> void { cout << s << a << endl; } );

  Box ca;

  ca.callIt ();

  ca.setFun (f);

  ca.callIt ();

  ca.setFun ( [&] (string s) -> void { cout << s << a << endl; } );

  ca.callIt ();

  ca.setFun (fc);

  ca.callIt ();

} // ()
cibercitizen1
fuente
2
No necesitas funValid: en.cppreference.com/w/cpp/utility/functional/function/… , solo diif ( ! theFunction )
Erik Aronesty