funciones c++ lambda functional-programming c++11

¿Por qué las funciones lambda en c++ 11 no tienen tipos de función<>?



lambda c++ (2)

Estoy jugando con las características funcionales de c ++ 11. Una cosa que me parece extraña es que el tipo de una función lambda en realidad NO es un tipo de función <>. Es más, los lambda no parecen jugar realmente bien con el mecanismo de inferencia de tipos.

Se adjunta un pequeño ejemplo en el que probé cambiar los dos argumentos de una función para agregar dos enteros. (El compilador que usé era gcc 4.6.2 bajo MinGW). En el ejemplo, el tipo para addInt_f se ha definido explícitamente usando la función <> mientras que addInt_l es un lambda cuyo tipo es de tipo addInt_l con auto .

Cuando compilé el código, la función flip puede aceptar la versión explícitamente definida por tipo de addInt pero no la versión lambda, dando un error que dice que, testCppBind.cpp:15:27: error: no matching function for call to ''flip(<lambda(int, int)>&)''

Las siguientes líneas muestran que se puede aceptar la versión lambda (así como una versión ''sin procesar'') si se convierte explícitamente al tipo de función apropiado <>.

Así que mis preguntas son:

  1. ¿Por qué es que una función lambda no tiene un tipo de function<> en primer lugar? En el pequeño ejemplo, ¿por qué addInt_l tiene la function<int (int,int)> como tipo en lugar de tener un tipo lambda diferente? Desde la perspectiva de la programación funcional, ¿cuál es la diferencia entre una función / objeto funcional y un lambda?

  2. Si hay una razón fundamental por la que estos dos tienen que ser diferentes. Escuché que los lambda se pueden convertir para function<> pero son diferentes. ¿Es este un problema / defecto de diseño de C ++ 11, un problema de implementación o hay un beneficio en distinguir a los dos como están? Parece que la firma de tipo de addInt_l solo ha proporcionado suficiente información sobre el parámetro y los tipos de retorno de la función.

  3. ¿Hay alguna forma de escribir la lambda para evitar la conversión explícita de tipos mencionada anteriormente?

Gracias por adelantado.

//-- testCppBind.cpp -- #include <functional> using namespace std; using namespace std::placeholders; template <typename T1,typename T2, typename T3> function<T3 (T2, T1)> flip(function<T3 (T1, T2)> f) { return bind(f,_2,_1);} function<int (int,int)> addInt_f = [](int a,int b) -> int { return a + b;}; auto addInt_l = [](int a,int b) -> int { return a + b;}; int addInt0(int a, int b) { return a+b;} int main() { auto ff = flip(addInt_f); //ok auto ff1 = flip(addInt_l); //not ok auto ff2 = flip((function<int (int,int)>)addInt_l); //ok auto ff3 = flip((function<int (int,int)>)addInt0); //ok return 0; }


  1. Porque la function<> emplea el borrado de tipos . Esto permite que se almacenen varios tipos de funciones diferentes en una function<> , pero incurre en una pequeña penalización de tiempo de ejecución. El borrado de tipo oculta el tipo real (su lambda específico) detrás de una interfaz de función virtual.
  2. Hay un beneficio para esto: uno de los "axiomas" de diseño de C ++ es nunca agregar gastos generales a menos que sea realmente necesario. Al usar esta configuración, no tiene ninguna sobrecarga cuando usa la inferencia de tipo (use auto o pass como parámetro de plantilla), pero aún tiene toda la flexibilidad para interactuar con el código que no es de plantilla a través de la function<> . También tenga en cuenta que la function<> no es una construcción de lenguaje, sino un componente de la biblioteca estándar que puede implementarse utilizando características de lenguaje simples.
  3. No, pero puede escribir la función para simplemente tomar el tipo de la función (construcción de lenguaje) en lugar de los detalles específicos de la function<> (construcción de biblioteca). Por supuesto, eso hace que sea mucho más difícil escribir el tipo de retorno, ya que no le da directamente los tipos de parámetros. Sin embargo, al usar alguna meta-programación a la Boost.FunctionTypes , puede deducirlos a partir de la función que pasa. Sin embargo, hay algunos casos en los que esto no es posible, por ejemplo, con functores que tienen un operator() plantilla operator() .

std::function es una herramienta útil para almacenar cualquier tipo de objeto llamable sin importar su tipo. Para hacer esto necesita emplear algún tipo de técnica de borrado, y eso implica algo de sobrecarga.

Cualquier llamada puede convertirse implícitamente en una std::function , y es por eso que generalmente funciona sin problemas.

Repetiré para asegurarme de que quede claro: std::function no es algo solo para lambdas o punteros de función: es para cualquier tipo de llamada. Eso incluye cosas como struct some_callable { void operator()() {} }; , por ejemplo. Eso es simple, pero podría ser algo como esto en su lugar:

struct some_polymorphic_callable { template <typename T> void operator()(T); };

Un lambda es solo otro objeto llamable, similar a las instancias del objeto some_callable anterior. Se puede almacenar en una std::function porque es llamable, pero no tiene la sobrecarga de borrado de tipo de la std::function .

Y el comité planea hacer las lambdas polimórficas en el futuro, es decir, las lambdas que se parecen a some_polymorphic_callable como some_polymorphic_callable . ¿Qué tipo de std::function sería tal lambda?

Ahora ... Deducción de parámetros de plantilla, o conversiones implícitas. Elegir uno. Esa es una regla de las plantillas de C ++.

Para pasar un lambda como un argumento std::function , se debe convertir implícitamente. Tomar un argumento std::function significa que está eligiendo conversiones implícitas sobre la deducción de tipo. Pero su plantilla de función necesita la firma para ser deducida o proporcionada explícitamente.

¿La solución? No restrinja sus llamadas a la std::function . Acepta cualquier tipo de reclamo .

template <typename Fun> auto flip(Fun&& f) -> decltype(std::bind(std::forward<Fun>(f),_2,_1)) { return std::bind(std::forward<Fun>(f),_2,_1); }

Ahora puedes estar pensando por qué necesitamos std::function entonces. std::function proporciona el borrado de tipo para callables con una firma conocida. Eso esencialmente hace que sea útil almacenar callables borrados de tipo y escribir interfaces virtual .