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 mutable
palabra clave? Es bastante diferente del paso de parámetros tradicionales a funciones con nombre. ¿Cuál es la razón detrás?
Tenía la impresión de que el objetivo de la captura por valor es permitir que el usuario cambie el temporal; de lo contrario, casi siempre estoy mejor usando la captura por referencia, ¿no?
¿Alguna iluminación?
(Por cierto, estoy usando MSVC2010. AFAIK esto debería ser estándar)
const
por defecto!const
por defecto.Respuestas:
Requiere
mutable
porque, de manera predeterminada, un objeto de función debería 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, efectivamente.fuente
void f(const std::function<int(int)> g)
. ¿Cómo puedo garantizar queg
sea realmente referencialmente transparente ?g
El proveedor de 'podría haber utilizado demutable
todos modos. Entonces no lo sabré. Por otro lado, si el valor predeterminado es non-const
, y las personas deben agregar enconst
lugar demutable
objetos de función, el compilador puede hacer cumplir laconst std::function<int(int)>
parte y ahoraf
puede asumir queg
esconst
, ¿no?Su código es casi equivalente a esto:
Por lo tanto, podría pensar que lambdas genera una clase con operator () que por defecto es constante, a menos que 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.
fuente
const
omutable
lambda si se implementara como tipos equivalentes definidos por el usuario, la pregunta es (como en el título y elaborado por OP en los comentarios) por quéconst
es el valor predeterminado, por lo que esto no lo responde.La pregunta es, ¿es "casi"? Un caso de uso frecuente parece ser devolver o pasar lambdas:
Creo que
mutable
no es un caso de "casi". Considero que "captura por valor" como "permitirme usar su valor después de que la entidad capturada muera" en lugar de "permitirme cambiar una copia de ella". Pero tal vez esto pueda ser discutido.fuente
const
? ¿Qué propósito logra?mutable
parece fuera de lugar aquí, cuando noconst
es el valor predeterminado en "casi" (: P) todo lo demás del lenguaje.const
fuera el valor predeterminado, al menos las personas se verían obligadas a considerar la corrección const: /const
que puedan llamarlo si el objeto lambda es constante o no. Por ejemplo, podrían pasarlo a una función tomando astd::function<void()> const&
. Para permitir que el lambda cambie sus copias capturadas, en los documentos iniciales los miembros de datos del cierre se definieronmutable
internamente de forma automática. Ahora tiene que poner manualmentemutable
la expresión lambda. Sin embargo, no he encontrado una justificación detallada.FWIW, Herb Sutter, un miembro bien conocido del comité de estandarización de C ++, ofrece una respuesta diferente a esa pregunta en Corriente de Lambda y Problemas de usabilidad :
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.
fuente
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 llamada a la función
::operator()
implementada. Cuando captura una variable utilizando una copia por valor , el compilador creará un nuevoconst
atributo 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 por la que llámelo " cierre ", porque de alguna manera, está cerrando su expresión Lambda copiando las variables del alcance superior en el alcance Lambda.mutable
, la entidad capturada se convertirá en unnon-const
atributo de su tipo de cierre. Esto es lo que hace que los cambios realizados en la variable mutable capturados por el valor, no se propaguen al alcance superior, sino que se mantengan dentro del Lambda con estado. Siempre trate de imaginar el tipo de cierre resultante de su expresión Lambda, que me ayudó mucho, y espero que también pueda ayudarlo.fuente
Ver este borrador , en 5.1.2 [expr.prim.lambda], subcláusula 5:
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. Sin embargo, no sé si es bueno.
Edite en el comentario de kizzx2: la mayoría de las veces cuando se va a usar un lambda es como un functor para algoritmos. El valor predeterminado
const
permite que se use en un entorno constante, al igual que lasconst
funciones calificadas normales se pueden usar allí, pero las noconst
calificadas no. Tal vez solo pensaron en hacerlo más intuitivo para esos casos, que saben lo que sucede en su mente. :)fuente
const
por defecto? Ya obtuve una copia nueva, parece extraño que no me dejen cambiarla, especialmente porque no es algo principalmente malo, solo quieren que agreguemutable
.var
una palabra clave para permitir el cambio y ser el valor predeterminado constante para todo lo demás. Ahora no, así que tenemos que vivir con eso. En mi opinión, C ++ 2011 salió bastante bien, teniendo en cuenta todo.n
No es temporal. n es un miembro del objeto de función lambda que crea con la expresión lambda. La expectativa predeterminada es que llamar a su lambda no modifica su estado, por lo tanto, es constante para evitar que lo modifique accidentalmenten
.fuente
¡Tienes que entender lo que significa capturar! ¡está capturando no pasando argumentos! Veamos algunos ejemplos de código:
Como puede ver a pesar de que
x
se ha cambiado a20
lambda, todavía está devolviendo 10 (x
todavía está5
dentro de la lambda) Cambiarx
dentro de la lambda significa cambiar la lambda en cada llamada (la lambda está mutando en cada llamada). Para hacer cumplir la corrección, el estándar introdujo lamutable
palabra clave. Al especificar una lambda como mutable, está diciendo que cada llamada a la lambda podría causar un cambio en la lambda misma. Veamos otro ejemplo:El ejemplo anterior muestra que al hacer que la lambda sea mutable, cambiar
x
dentro de la lambda "muta" la lambda en cada llamada con un nuevo valorx
que no tiene nada que ver con el valor real dex
en la función principalfuente
Ahora hay una propuesta para aliviar la necesidad de las
mutable
declaraciones lambda: n3424fuente
mutable
incluso es una palabra clave en C ++.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 las 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 una lambda captura por referencia, escribir en variables capturadas es una carga para el concepto de función pura, porque todo lo que una función pura debería hacer es devolver una salida, aunque la lambda ciertamente no muta porque la escritura sucede a variables externas. Incluso en este caso, un uso correcto implica que si se llama a la lambda con la misma entrada nuevamente, la salida será la misma cada vez, a pesar de estos efectos secundarios en las variables by-ref. Tales 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.
fuente