funciones examples c++ lambda c++11

examples - ¿Por qué la lambda de C++ 11 requiere la palabra clave "mutable" para la captura por valor, de forma predeterminada?



lambda examples c++ (10)

Breve ejemplo:

#include <iostream> int main() { int n; [&](){n = 10;}(); // OK [=]() mutable {n = 20;}(); // OK // [=](){n = 10;}(); // Error: a by-value capture cannot be modified in a non-mutable lambda std::cout << n << "/n"; // "10" }

La pregunta: ¿Por qué necesitamos la palabra clave mutable ? Es bastante diferente del paso tradicional de parámetros a las funciones nombradas. ¿Cuál es la razón detrás?

Tenía la impresión de que todo el punto de captura por valor es permitir que el usuario cambie el temporal; de lo contrario, casi siempre es mejor usar la captura por referencia, ¿no es así?

¿Alguna iluminación?

(Por cierto, estoy usando MSVC2010. AFAIK debería ser estándar)


Tenía la impresión de que todo el punto de captura por valor es permitir que el usuario cambie el temporal; de lo contrario, casi siempre es mejor usar la captura por referencia, ¿no es así?

La pregunta es, ¿es "casi"? Un caso de uso frecuente parece ser devolver o pasar lambdas:

void registerCallback(std::function<void()> f) { /* ... */ } void doSomething() { std::string name = receiveName(); registerCallback([name]{ /* do something with name */ }); }

Creo que mutable no es un caso de "casi". Considero que "captura por valor" me gusta "me permite usar su valor después de que la entidad capturada muere" en lugar de "me permite cambiar una copia de la misma". Pero tal vez esto se pueda argumentar.


Tenía la impresión de que todo el punto de captura por valor es permitir que el usuario cambie el temporal; de lo contrario, casi siempre es mejor usar la captura por referencia, ¿no es así?

n no es un temporal. n es un miembro del lambda-function-object que creas con la expresión lambda. La expectativa predeterminada es que al llamar a su lambda no modifique su estado, por lo tanto, es constante evitar que modifique accidentalmente n .


Ahora hay una propuesta para aliviar la necesidad de mutable en las declaraciones lambda: n3424


Cuando usas lambda como parámetro de función, la respuesta se destaca!


Debe pensar cuál es el tipo de cierre de su función Lambda. Cada vez que declara una expresión Lambda, el compilador crea un tipo de cierre, que es nada menos que una declaración de clase sin nombre con atributos ( entorno donde se declaró la expresión Lambda) y la función call ::operator() implementada. Cuando captura una variable usando copia por valor , el compilador creará un nuevo atributo const en el tipo de cierre, por lo que no puede cambiarlo dentro de la expresión Lambda porque es un atributo de "solo lectura", esa es la razón lo llaman un " cierre ", porque de alguna manera, usted está cerrando su expresión Lambda copiando las variables del ámbito superior en el ámbito Lambda. Cuando utiliza la palabra clave mutable , la entidad capturada se convertirá en un atributo non-const de su tipo de cierre. Esto es lo que hace que los cambios realizados en la variable mutable capturada por valor, no se propaguen al alcance superior, sino que se mantengan dentro del estado Lambda. Siempre intente imaginar el tipo de cierre resultante de su expresión Lambda, eso me ayudó mucho, y espero que también pueda ayudarlo a usted.


FWIW, Herb Sutter, un miembro bien conocido del comité de estandarización de C ++, ofrece una respuesta diferente a esa pregunta en Cuestiones de corrección y usabilidad de Lambda :

Considere este ejemplo del hombre de paja, donde el programador captura una variable local por valor e intenta modificar el valor capturado (que es una variable miembro del objeto lambda):

int val = 0; auto x = [=](item e) // look ma, [=] means explicit copy { use(e,++val); }; // error: count is const, need ‘mutable’ auto y = [val](item e) // darnit, I really can’t get more explicit { use(e,++val); }; // same error: count is const, need ‘mutable’

Esta característica parece haber sido agregada debido a la preocupación de que el usuario podría no darse cuenta de que recibió una copia, y en particular que, dado que las lambdas son copiables, podría estar cambiando una copia de la lambda diferente.

Su artículo trata sobre por qué esto debería cambiarse en C ++ 14. Es breve, está bien escrito, vale la pena leerlo si quiere saber "qué hay en la mente [de los miembros del comité]" con respecto a esta característica en particular.


Para extender la respuesta de Puppy, las funciones lambda están destinadas a ser funciones puras . Eso significa que cada llamada dada un conjunto de entrada único siempre devuelve la misma salida. Definamos la entrada como el conjunto de todos los argumentos más todas las variables capturadas cuando se llama a lambda.

En funciones puras, la salida depende únicamente de la entrada y no de algún estado interno. Por lo tanto, cualquier función lambda, si es pura, no necesita cambiar su estado y, por lo tanto, es inmutable.

Cuando un lambda captura por referencia, escribir sobre las variables capturadas es una tensión sobre el concepto de función pura, ya que toda la función pura debería hacer es devolver una salida, aunque el lambda ciertamente no muta porque la escritura pasa a variables externas. Incluso en este caso, un uso correcto implica que si se llama a la lambda con la misma entrada de nuevo, la salida será la misma cada vez, a pesar de estos efectos secundarios en las variables de referencia. Dichos efectos secundarios son solo formas de devolver alguna entrada adicional (por ejemplo, actualizar un contador) y podrían reformularse en una función pura, por ejemplo, devolver una tupla en lugar de un solo valor.


Requiere mutable porque, de forma predeterminada, un objeto de función debe producir el mismo resultado cada vez que se llama. Esta es la diferencia entre una función orientada a objetos y una función que utiliza una variable global, de manera efectiva.


Su código es casi equivalente a esto:

#include <iostream> class unnamed1 { int& n; public: unnamed1(int& N) : n(N) {} /* OK. Your this is const but you don''t modify the "n" reference, but the value pointed by it. You wouldn''t be able to modify a reference anyway even if your operator() was mutable. When you assign a reference it will always point to the same var. */ void operator()() const {n = 10;} }; class unnamed2 { int n; public: unnamed2(int N) : n(N) {} /* OK. Your this pointer is not const (since your operator() is "mutable" instead of const). So you can modify the "n" member. */ void operator()() {n = 20;} }; class unnamed3 { int n; public: unnamed3(int N) : n(N) {} /* BAD. Your this is const so you can''t modify the "n" member. */ void operator()() const {n = 10;} }; int main() { int n; unnamed1 u1(n); u1(); // OK unnamed2 u2(n); u2(); // OK //unnamed3 u3(n); u3(); // Error std::cout << n << "/n"; // "10" }

Entonces, podría pensar que las lambdas generan una clase con operator () que por defecto es const, a menos que usted diga que es mutable.

También puede pensar en todas las variables capturadas dentro de [] (explícita o implícitamente) como miembros de esa clase: copias de los objetos para [=] o referencias a los objetos para [&]. Se inicializan cuando declara su lambda como si hubiera un constructor oculto.


Vea este borrador , en 5.1.2 [expr.prim.lambda], subcláusula 5:

El tipo de cierre para una expresión lambda tiene un operador de llamada de función en línea pública (13.5.4) cuyos parámetros y tipo de retorno se describen mediante la cláusula de declaración de parámetros de la expresión lambda y el tipo de retorno de trailing respectivamente. Este operador de llamada de función se declara const (9.3.1) si y solo si la cláusula-declaración-parámetro de lambdaexpression no es seguida por mutable.

Edite el comentario de litb: ¿Tal vez pensaron en la captura por valor para que los cambios externos a las variables no se reflejen dentro de la lambda? Las referencias funcionan en ambos sentidos, así que esa es mi explicación. Aunque no sé si es bueno.

Edite el comentario de kizzx2: la mayoría de las veces cuando se utiliza un lambda es como un functor para algoritmos. La const predeterminada permite que se use en un entorno constante, al igual que las funciones calificadas por const normales, pero no las calificadas por const . Tal vez solo pensaron en hacerlo más intuitivo para esos casos, quienes saben lo que pasa por su mente. :)