c++ funciones expression - ¿Qué es una expresión lambda en C ++ 11?





4 Answers

¿Qué es una función lambda?

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

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é elementos externos deben estar disponibles 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 funcion

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

Tipo de devolución deducción

Si un lambda tiene solo una declaración de retorno, el tipo de retorno se puede omitir 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 capturados por valor.

Casos de uso

La biblioteca definida por la norma ISO se beneficia en gran medida de las 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 las lambdas se han extendido por varias propuestas.

Capturas Lambda Inicializadas

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

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 una tomada 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 tipo de plantilla en algún lugar del ámbito que lo rodea):

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

Deducción del tipo de retorno mejorado

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

closure c# function

¿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 introducción?

Unos pocos ejemplos, y casos de uso serían útiles.




Respuestas

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

R: Bajo el capó, es el objeto de una clase autogenerada con const de operador de sobrecarga () . Tal objeto se llama cierre y creado por el compilador. Este concepto de "cierre" se acerca al concepto de enlace de C ++ 11. Pero las lambdas suelen generar mejor código. Y las llamadas a través de los cierres permiten la inclusión completa.

P: ¿Cuándo usaría uno?

R: Para definir "lógica simple y pequeña" y pedir al compilador que realice una generación a partir de la pregunta anterior. Le das a un compilador algunas expresiones que quieres que estén dentro de operator (). Todo lo demás compilador te generará.

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

R: Es una especie de azúcar de sintaxis como operadores que sobrecargan en lugar de funciones para operaciones de adición, sobraactividad personalizada ... ¡Pero guarda más líneas de código innecesario para ajustar 1-3 líneas de lógica real a algunas clases, y etc.! Algunos ingenieros piensan que si el número de líneas es más pequeño, 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 cubiertas por pregunta. Ignora esta sección si no estás interesado.

1. Valores capturados. Lo que puedas capturar

1.1. Puede hacer referencia a una variable con duración de almacenamiento estático en lambdas. Todos ellos son capturados.

1.2. Puede utilizar lambda para los valores de captura "por valor". En tal caso, las variables capturadas se copiarán al objeto de función (cierre).

[captureVar1,captureVar2](int arg1){}

1.3. Se puede capturar como referencia. & - en este contexto significa referencia, no punteros.

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

1.4. Existe notación 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. Existe una notación para capturar todas las vars no estáticas por valor, o por referencia y especificar algo. Más. Ejemplos: Capture todas las vars no estáticas por valor, pero por captura de referencia Param2

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

Capture todos los vars no estáticos por referencia, pero por captura de valor Param2

[&,Param2](int arg1){} 

2. Devolución del tipo de devolución.

2.1. El tipo de retorno Lambda se puede deducir si lambda 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 retorno debe especificarse a través del tipo de retorno final. Además, se puede aplicar una sintaxis similar a las funciones automáticas y las funciones miembro.

3. Valores capturados. Lo que no puedes capturar

3.1. Puede capturar solo vars locales, no la variable miembro del objeto.

4. Revisiones

4.1 !! Lambda no es un puntero de función y no es una función anónima, pero las lambdas sin captura pueden convertirse implícitamente en un puntero de función.

PD

  1. Puede encontrar más información sobre la gramática lambda en el borrador de trabajo para el lenguaje de programación C ++ # 337, 2012-01-16, 5.1.2. Expresiones de Lambda, p.88

  2. En C ++ 14 se ha agregado la característica adicional que se ha denominado como "captura de inicio". Permite realizar 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);};
    



Una de las mejores explicaciones de la lambda expression es la 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 conocida como una función lambda o (estrictamente hablando de manera incorrecta, pero coloquial) como una lambda , es una notación simplificada para definir y utilizar un objeto de función anónimo . En lugar de definir una clase nombrada 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 un 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 de su código y una forma de hacerlo más atractivo. Como triste por Stroustup:

formas efectivas de optimización

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 vía 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 necesitas, puedes 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 supongamos 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á siguiente

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 usarla.

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 utilizar nombres locales del contexto circundante en el cuerpo lambda. Para tales expresiones lambda, los datos se obtienen de argumentos o de variables no locales.

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

[=] : captura implícitamente por valor. Se pueden utilizar todos los nombres locales. 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, se almacenarán 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 los nombres seguidos de ... como elementos.

[&, lista de captura] : 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 ir precedidos por &. Las variables nombradas en la lista de captura se capturan por valor.

[=, lista de captura] : 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 ir 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:




Bueno, un uso práctico que he descubierto es reducir el 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, puede 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.






Related