[c++] ¿Qué es una expresión lambda en C ++ 11?


3 Answers

¿Qué es una función lambda?

El concepto de C ++ de una función lambda se origina en el cálculo lambda y la programación funcional. Una lambda es una función sin nombre que es útil (en la programación real, no en la teoría) para fragmentos de código cortos que son imposibles de reutilizar y que no vale la pena nombrar.

En C ++, una función lambda se define así

[]() { } // barebone lambda

o en todo su esplendor

[]() mutable -> T { } // T is the return type, still lacking throw()

[] es la lista de captura, () la lista de argumentos y {} el cuerpo de la función.

La lista de captura

La lista de captura define qué parte del exterior de la lambda debería estar disponible dentro del cuerpo de la función y cómo. Puede ser:

  1. un valor: [x]
  2. una referencia [& x]
  3. cualquier variable actualmente en alcance por referencia [&]
  4. Igual que 3, pero por valor [=]

Puede mezclar cualquiera de los anteriores en una lista separada por comas [x, &y] .

La lista de argumentos

La lista de argumentos es la misma que en cualquier otra función de C ++.

El cuerpo de la función

El código que se ejecutará cuando se llame realmente a la lambda.

Devolución de tipo de devolución

Si un lambda tiene solo una declaración de retorno, el tipo de retorno puede ser omitido y tiene el tipo implícito de decltype(return_statement) .

Mudable

Si una lambda está marcada como mutable (por ejemplo, []() mutable { } ), se permite mutar los valores que se han capturado por valor.

Casos de uso

La biblioteca definida por el estándar ISO se beneficia enormemente de lambdas y aumenta la usabilidad de varias barras, ya que ahora los usuarios no tienen que saturar su código con pequeños funtores en algún ámbito accesible.

C ++ 14

En C ++ 14 lambdas han sido ampliadas por varias propuestas.

Capturas de Lambda inicializadas

Un elemento de la lista de captura ahora se puede inicializar con = . Esto permite renombrar variables y capturar moviendo. Un ejemplo tomado del estándar:

int x = 4;
auto y = [&r = x, x = x+1]()->int {
            r += 2;
            return x+2;
         }();  // Updates ::x to 6, and initializes y to 7.

y uno tomado de Wikipedia que muestra cómo capturar con std::move :

auto ptr = std::make_unique<int>(10); // See below for std::make_unique
auto lambda = [ptr = std::move(ptr)] {return *ptr;};

Lambdas Genérico

Lambdas ahora puede ser genérico ( auto sería equivalente a T aquí si T fuera un argumento de plantilla de tipo en algún lugar del ámbito circundante):

auto lambda = [](auto x, auto y) {return x + y;};

Deducción mejorada del tipo de devolución

C ++ 14 permite tipos de retorno deducidos para cada función y no lo restringe a funciones de la return expression; formulario return expression; . Esto también se extiende a lambdas.

Question

¿Qué es una expresión lambda en C ++ 11? ¿Cuándo usaría uno? ¿Qué clase de problema resuelven que no fue posible antes de su presentación?

Algunos ejemplos y casos de uso serían útiles.




Bueno, un uso práctico que he descubierto es la reducción del código de la placa de la caldera. Por ejemplo:

void process_z_vec(vector<int>& vec)
{
  auto print_2d = [](const vector<int>& board, int bsize)
  {
    for(int i = 0; i<bsize; i++)
    {
      for(int j=0; j<bsize; j++)
      {
        cout << board[bsize*i+j] << " ";
      }
      cout << "\n";
    }
  };
  // Do sth with the vec.
  print_2d(vec,x_size);
  // Do sth else with the vec.
  print_2d(vec,y_size);
  //... 
}

Sin lambda, es posible que deba hacer algo para diferentes casos de bsize . Por supuesto, podría crear una función, pero ¿qué sucede si desea limitar el uso dentro del alcance de la función de usuario del alma? la naturaleza de lambda cumple con este requisito y lo uso para ese caso.




Una de las mejores explicaciones de la lambda expression de lambda expression proviene del autor de C ++ Bjarne Stroustrup en su libro ***The C++ Programming Language*** capítulo 11 ( ISBN-13: 978-0321563842 ):

What is a lambda expression?

Una expresión lambda , a veces también denominada función lambda o (estrictamente hablando incorrectamente, pero coloquialmente) como lambda , es una notación simplificada para definir y usar un objeto de función anónimo . En lugar de definir una clase con un operador (), luego hacer un objeto de esa clase, y finalmente invocarlo, podemos usar una taquigrafía.

When would I use one?

Esto es particularmente útil cuando queremos pasar una operación como argumento a un algoritmo. En el contexto de las interfaces gráficas de usuario (y en otros lugares), tales operaciones a menudo se denominan devoluciones de llamada .

What class of problem do they solve that wasn't possible prior to their introduction?

Aquí supongo que cada acción realizada con la expresión lambda se puede resolver sin ellos, pero con mucho más código y una complejidad mucho mayor. Expresión Lambda esta es la forma de optimización para su código y una forma de hacerlo más atractivo. Como triste por Stroustup:

formas efectivas de optimizar

Some examples

a través de la expresión lambda

void print_modulo(const vector<int>& v, ostream& os, int m) // output v[i] to os if v[i]%m==0
{
    for_each(begin(v),end(v),
        [&os,m](int x) { 
           if (x%m==0) os << x << '\n';
         });
}

o a través de la función

class Modulo_print {
         ostream& os; // members to hold the capture list int m;
     public:
         Modulo_print(ostream& s, int mm) :os(s), m(mm) {} 
         void operator()(int x) const
           { 
             if (x%m==0) os << x << '\n'; 
           }
};

o incluso

void print_modulo(const vector<int>& v, ostream& os, int m) 
     // output v[i] to os if v[i]%m==0
{
    class Modulo_print {
        ostream& os; // members to hold the capture list
        int m; 
        public:
           Modulo_print (ostream& s, int mm) :os(s), m(mm) {}
           void operator()(int x) const
           { 
               if (x%m==0) os << x << '\n';
           }
     };
     for_each(begin(v),end(v),Modulo_print{os,m}); 
}

si lo necesita, puede nombrar la lambda expression como a continuación:

void print_modulo(const vector<int>& v, ostream& os, int m)
    // output v[i] to os if v[i]%m==0
{
      auto Modulo_print = [&os,m] (int x) { if (x%m==0) os << x << '\n'; };
      for_each(begin(v),end(v),Modulo_print);
 }

O suponga otra muestra simple

void TestFunctions::simpleLambda() {
    bool sensitive = true;
    std::vector<int> v = std::vector<int>({1,33,3,4,5,6,7});

    sort(v.begin(),v.end(),
         [sensitive](int x, int y) {
             printf("\n%i\n",  x < y);
             return sensitive ? x < y : abs(x) < abs(y);
         });


    printf("sorted");
    for_each(v.begin(), v.end(),
             [](int x) {
                 printf("x - %i;", x);
             }
             );
}

generará el próximo

0

1

0

1

0

1

0

1

0

1

0 sortedx - 1; x - 3; x - 4; x - 5; x - 6; x - 7; x - 33;

[] - esta es la lista de captura o lambda introducer : si las lambdas no requieren acceso a su entorno local, podemos usarlo.

Cita del libro:

El primer carácter de una expresión lambda es siempre [ . Un introductor lambda puede tomar varias formas:

[] : una lista de captura vacía. Esto implica que no se pueden usar nombres locales del contexto circundante en el cuerpo lambda. Para tales expresiones lambda, los datos se obtienen a partir de argumentos o de variables no locales.

[&] : captura implícitamente por referencia. Todos los nombres locales pueden ser utilizados. Se accede a todas las variables locales por referencia.

[=] : capture implícitamente por valor. Todos los nombres locales pueden ser utilizados. Todos los nombres se refieren a copias de las variables locales tomadas en el punto de llamada de la expresión lambda.

[lista de captura]: captura explícita; la lista de captura es la lista de nombres de variables locales que se capturarán (es decir, almacenadas en el objeto) por referencia o por valor. Las variables con nombres precedidos por & se capturan por referencia. Otras variables son capturadas por valor. Una lista de captura también puede contener esto y nombres seguidos por ... como elementos.

[&, capture-list] : captura implícitamente por referencia todas las variables locales con nombres no mencionados en la lista. La lista de captura puede contener esto. Los nombres listados no pueden ser precedidos por &. Las variables nombradas en la lista de captura se capturan por valor.

[=, capture-list] : captura implícitamente por valor todas las variables locales con nombres no mencionados en la lista. La lista de captura no puede contener esto. Los nombres listados deben estar precedidos por &. Las variables nombradas en la lista de captura se capturan por referencia.

Tenga en cuenta que un nombre local precedido por & siempre se captura por referencia y un nombre local no precedido por & siempre se captura por valor. Solo la captura por referencia permite la modificación de variables en el entorno de llamada.

Additional

Formato de Lambda expression

Referencias adicionales:




Respuestas

P: ¿Qué es una expresión lambda en C ++ 11?

A: Debajo del capó es objeto de clase autogenerada con overloading operator () const . Tal objeto se llama cierre y creado por el compilador. Este concepto de "cierre" está cerca del concepto de enlace de C ++ 11. Pero las lambdas generalmente generan un mejor código. Y las llamadas a través de cierres permiten la creación de líneas completas.

P: ¿Cuándo usaría uno?

R: Para definir "lógica simple y pequeña" y preguntar al compilador que realice la generación de la pregunta anterior. Le das al compilador algunas expresiones que quieres que sean dentro del operador (). Todo el compilador de otras cosas te generará.

P: ¿Qué clase de problema resuelven que no fue posible antes de su presentación?

R: Es un tipo de azúcar sintáctica como la sobrecarga de operadores en lugar de funciones para operaciones de adición, subcontratación personalizadas ... ¡Pero ahorra más líneas de código innecesario para envolver 1-3 líneas de lógica real en algunas clases, etc.! Algunos ingenieros piensan que si el número de líneas es más pequeño, entonces hay menos posibilidades de cometer errores (también lo creo)

Ejemplo de uso

auto x = [=](int arg1){printf("%i", arg1); };
void(*f)(int) = x;
f(1);
x(1);

Extras sobre lambdas, no cubiertos por pregunta. Ignora esta sección si no estás interesado

1. Valores capturados. Lo que puedes capturar

1.1. Puede hacer referencia a la variable con duración de almacenamiento estática en lamdas. Todos ellos son capturados.

1.2. Puede usar lamda para valores de captura "por valor". En tal caso, los vars capturados se copiarán al objeto de función (cierre).

[captureVar1,captureVar2](int arg1){}

1.3. Puedes capturar ser referencia. & - en este contexto, referencia media, no punteros.

   [&captureVar1,&captureVar2](int arg1){}

1.4. Es notación de existir para capturar todos los vars no estáticos por valor, o por referencia

  [=](int arg1){} // capture all not-static vars by value

  [&](int arg1){} // capture all not-static vars by reference

1.5. Es notación de existir para capturar todos los vars no estáticos por valor, o por referencia y especificar smth. Más. Ejemplos: capture todos los valores vars no estáticos por valor, pero por captura de referencia Param2

[=,&Param2](int arg1){} 

Capture todos los vars no estáticos por referencia, pero mediante la captura de valores Param2

[&,Param2](int arg1){} 

2. Deducción del tipo de devolución

2.1. El tipo de retorno Lambda se puede deducir si lamda es una expresión. O puede especificarlo explícitamente.

[=](int arg1)->trailing_return_type{return trailing_return_type();}

Si lambda tiene más de una expresión, entonces el tipo de devolución debe especificarse mediante el tipo de devolución final. También se puede aplicar una sintaxis similar a funciones automáticas y funciones de miembros

3. Valores capturados. Lo que no puedes capturar

3.1. Puede capturar solo variables locales, no variables de objeto.

4. onversions

4.1. lambda no es un puntero de función y no es una función anónima , sino que se puede convertir implícitamente en puntero de función.

PD

  1. Puede obtener más información acerca de la información de la información de la gramática lambda en el Borrador de Trabajo para el Lenguaje de Programación C ++ # 337, 2012-01-16, 5.1.2. Lambda Expressions, p.88

  2. En C ++ 14, se agregó la función extra que se ha denominado "captura de inicio". Permite realizar una declaración arbitraria de miembros de datos de cierre:

    auto toFloat = [](int value) { return float(value);};
    auto interpolate = [min = toFloat(0), max = toFloat(255)](int value)->float { return (value - min) / (max - min);};
    





Related