2012-07-24 17 views
17

Estoy jugando con las características funcionales de C++ 11. Una cosa que encuentro extraña es que el tipo de una función lambda en realidad NO es una función <> tipo. Además, las lambda no parecen jugar muy bien con el mecanismo de tipografía.¿Por qué las funciones lambda en C++ 11 no tienen función <> tipos?

Adjunto hay un pequeño ejemplo en el que probé volteando los dos argumentos de una función para agregar dos enteros. (El compilador que utilicé fue gcc 4.6.2 en MinGW). En el ejemplo, el tipo para addInt_f se ha definido explícitamente usando la función <> mientras que addInt_l es una lambda cuyo tipo está ingresado por tipo con auto.

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

Las siguientes líneas muestran que la versión lambda (así como también una versión 'en bruto') puede ser aceptado si se emite explícitamente a la función apropiada <> tipo.

Así que mis preguntas son:

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

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

  3. ¿Hay alguna manera de escribir la lambda para evitar el colado explícito de tipos mencionado anteriormente?

Gracias de antemano.

//-- 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; 
    } 

+4

No debería estar tomando argumentos 'std :: function', principalmente porque inhibe la deducción de tipo (que es su problema aquí). –

+1

Relacionado: [C++ 11 no deduce el tipo cuando están involucradas las funciones std :: function o lambda] (http://stackoverflow.com/q/9998402/487781) – hardmath

+1

Las lambdas se transforman en funtores anónimos (o funciones si no capturar un entorno). Transformarlos en std :: function introduciría un fuerte acoplamiento entre el lenguaje y la biblioteca y, por lo tanto, sería una muy mala idea. – MFH

Respuesta

39

std::function es una herramienta útil para tienda de cualquier tipo de objeto exigible independientemente de su tipo. Para hacer esto, necesita emplear algún tipo de técnica de borrado, y eso implica un poco de sobrecarga.

Cualquier llamable se puede convertir implícitamente en std::function, y es por eso que generalmente funciona a la perfección.

Voy a repetir para asegurarse de que se hace evidente: std::function no es algo sólo para lambdas o punteros de función: es para cualquier tipo de invocable. Eso incluye cosas como struct some_callable { void operator()() {} };, por ejemplo. Esto es muy simple, pero podría ser algo como esto en su lugar:

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

Un lambda es todavía un objeto más exigible, similar a instancias del objeto some_callable anteriormente. Se puede almacenar en un std::function porque es invocable, pero no tiene la tara de borrado de tipo std::function.

Y el comité planea hacer lambdas polimórficos en el futuro, es decir, lambdas que se ven como some_polymorphic_callable arriba. ¿Qué tipo de std::function sería una lambda?


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

Para pasar un lambda como un argumento std::function, debe convertirse 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 que la firma se deduzca o proporcione explícitamente.

¿La solución? No restrinja las personas que llaman al std::function. Acepte cualquier tipo de llamativo.

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 puede estar pensando por qué qué necesitamos std::function a continuación. std::function proporciona borrado de tipo para callables con una firma conocida. Eso esencialmente hace que sea útil para almacenar callables borrados por tipo y para escribir virtual interfaces.

+0

: Gracias por su clara explicación. En mi humilde opinión, el hecho de que std :: function solo se pueda usar para el almacenamiento es bastante decepcionante. Si entiendo tu respuesta correctamente, ¿esto es para evitar gastos generales? Entonces, ¿pasa lo mismo con los punteros inteligentes? Además, en el ejemplo de 'some_polymorphic_callable', no veo por qué el tipo de función <> no puede expresar el tipo para el lambda con plantilla ya que la función <> ya puede tener parámetros de plantilla. Si el único problema es la sobrecarga, ¿alguien ha considerado que una función ligera <> puede usarse en tipos de parámetros? Haría que STL sea mucho más útil. – tinlyx

+0

Además, en el ejemplo 'decltype', si el cuerpo de la función es de 25 líneas de código en lugar de una línea, ¿necesitamos incluir 25 líneas en decltype? Además, el uso de '' parece casi nulo * o una macro para mí. El mal uso de 'Fun' solo se puede detectar cuando se produce un error de compilación. En comparación, un tipo de parámetro de función <> (si se puede usar) le indicaría claramente a la persona y a la computadora la firma del tipo. Es cierto que tengo un conocimiento muy limitado sobre "borrado de tipo" y cosas por el estilo. Gracias por decirme las reglas. Me pregunto por qué tiene que ser así. – tinlyx

+0

@TingL El tipo para la lambda polimórfica no se puede expresar porque 'function <>' toma * un * argumento de firma, mientras que la lambda polimórfica tendría * un número ilimitado de firmas diferentes * (es por eso que se llama polimórfico: tiene muchas formas) El problema de sobrecarga no se aplica del mismo modo para los punteros inteligentes. 'unique_ptr' tiene una carga realmente * cero * (fue uno de los objetivos de diseño). 'shared_ptr' puede tener cierta sobrecarga si no está ejecutando un programa multiproceso, pero los programas multiproceso son un caso donde es más probable que se use. –

5
  1. Debido function<> emplea type erasure. Esto permite almacenar varios tipos de funciones diferentes en un function<>, pero incurre en una pequeña penalización de tiempo de ejecución. La borradura de tipo oculta el tipo real (su lambda específica) detrás de una interfaz de función virtual.
  2. Existe una ventaja en esto: uno de los "axiomas" de diseño C++ es nunca agregar sobrecarga a menos que realmente se necesite. Al usar esta configuración, no tiene ningún costo indirecto al usar la inferencia de tipo (use auto o pase como un parámetro de plantilla), pero aún tiene toda la flexibilidad para interactuar con código que no es de plantilla a través del function<>. También tenga en cuenta que function<> no es una construcción de lenguaje, sino un componente de la biblioteca estándar que puede implementarse utilizando funciones de lenguaje simples.
  3. No, pero puede escribir la función para simplemente tomar el tipo de la función (construcción del lenguaje) en lugar de los detalles del function<> (construcción de la biblioteca).Por supuesto, eso hace que sea mucho más difícil escribir el tipo de devolución, ya que no proporciona directamente los tipos de parámetros. Sin embargo, al usar meta-programación a la Boost.FunctionTypes puede deducir esto de la función que pasa. Sin embargo, en algunos casos esto no es posible, por ejemplo con functors que tienen una plantilla de operator().
Cuestiones relacionadas