Descubrí que los resultados son diferentes entre los compiladores si uso un lambda para capturar una referencia a una variable global con una palabra clave mutable y luego modifico el valor en la función lambda.
#include <stdio.h>
#include <functional>
int n = 100;
std::function<int()> f()
{
int &m = n;
return [m] () mutable -> int {
m += 123;
return m;
};
}
int main()
{
int x = n;
int y = f()();
int z = n;
printf("%d %d %d\n", x, y, z);
return 0;
}
Resultado de VS 2015 y GCC (g ++ (Ubuntu 5.4.0-6ubuntu1 ~ 16.04.12) 5.4.0 20160609):
100 223 100
Resultado de clang ++ (clang versión 3.8.0-2ubuntu4 (etiquetas / RELEASE_380 / final)):
100 223 223
¿Por qué pasó esto? ¿Está permitido por los estándares de C ++?
c++
c++11
lambda
language-lawyer
Colita
fuente
fuente

Respuestas:
Un lambda no puede capturar una referencia en sí por valor (uso
std::reference_wrapperpara tal fin).En su lambda, las
[m]capturasmpor valor (porque no hay ninguna&en la captura), por lo quem(siendo una referencia an) primero se desreferencia y se captura una copia de la cosa a la que hace referencia (n). Esto no es diferente a hacer esto:La lambda luego modifica esa copia, no el original. Eso es lo que está viendo suceder en las salidas VS y GCC, como se esperaba.
La salida de Clang es incorrecta, y debe informarse como un error, si aún no lo ha hecho.
Si desea que su lambda de modificar
n, la capturampor referencia en su lugar:[&m]. Esto no es diferente a asignar una referencia a otra, por ejemplo:O bien, puede simplemente deshacerse de
mtodo y capturanpor referencia en su lugar:[&n].Aunque, dado que
ntiene un alcance global, realmente no necesita ser capturado, la lambda puede acceder a él globalmente sin capturarlo:fuente
Creo que Clang en realidad puede ser correcto.
De acuerdo con [lambda.capture] / 11 , una expresión id utilizada en el lambda se refiere al miembro de lambda capturado por copia solo si constituye un uso odr . Si no es así, se refiere a la entidad original . Esto se aplica a todas las versiones de C ++ desde C ++ 11.
De acuerdo con [++.dev.odr] / 3 de C ++ 17, una variable de referencia no se utiliza odr si la conversión de valor-valor-valor produce una expresión constante.
Sin embargo, en el borrador de C ++ 20, el requisito para la conversión de valor a valor se cae y el pasaje relevante cambió varias veces para incluir o no la conversión. Consulte el número 1472 de CWG y el número 1741 de CWG , así como el número 2083 de CWG abierto .
Como
mse inicializa con una expresión constante (que se refiere a un objeto de duración de almacenamiento estático), su uso produce una expresión constante por excepción en [expr.const] /2.11.1 .Sin embargo, este no es el caso si se aplican las conversiones de valor a valor, porque el valor de
nno es utilizable en una expresión constante.Por lo tanto, dependiendo de si se supone que las conversiones lvalue-to-rvalue se deben aplicar para determinar el uso de odr, cuando se usa
men el lambda, puede referirse o no al miembro del lambda.Si se debe aplicar la conversión, GCC y MSVC son correctos, de lo contrario, Clang lo es.
Puede ver que Clang cambia su comportamiento si cambia la inicialización de
mpara que ya no sea una expresión constante:En este caso, todos los compiladores están de acuerdo en que la salida es
porque
men lambda se referirá al miembro del cierre que es de tipointcopy-initialized de la variable de referenciamenf.fuente
mes odr-utilizada por una expresión nombrándola a menos que la aplicación de la conversión lvalue-to-rvalue sea una expresión constante. Por [expr.const] / (2.7), esa conversión no sería una expresión constante central.m += 123;Aquímestá odr-used.Esto no está permitido por el estándar C ++ 17, pero por otros borradores estándar podría estarlo. Es complicado, por razones no explicadas en esta respuesta.
[expr.prim.lambda.capture] / 10 :
Los
[m]medios de que la variablemenfes capturado por la copia. La entidadmes una referencia al objeto, por lo que el tipo de cierre tiene un miembro cuyo tipo es el tipo referenciado. Es decir, el tipo de miembro esinty noint&.Dado que el nombre
mdentro del cuerpo lambda nombra el miembro del objeto de cierre y no la variable enf(y esta es la parte cuestionable), la instrucciónm += 123;modifica ese miembro, que es unintobjeto diferente de::n.fuente