Descobri que os resultados são diferentes nos compiladores se eu usar um lambda para capturar uma referência à variável global com palavra-chave mutável e modificar o valor na função 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 do VS 2015 e GCC (g ++ (Ubuntu 5.4.0-6ubuntu1 ~ 16.04.12) 5.4.0 20160609):
100 223 100
Resultado do clang ++ (versão 3.8.0-2ubuntu4 (tags / RELEASE_380 / final)):
100 223 223
Por que isso acontece? Isso é permitido pelos padrões C ++?
c++
c++11
lambda
language-lawyer
Willy
fonte
fonte
Respostas:
A lambda não pode capturar uma referência em si por valor (uso
std::reference_wrapper
para esse fim).Em seu lambda, as
[m]
capturasm
por valor (porque não há&
na captura), portantom
(sendo uma referência an
) é primeiro desreferenciado e uma cópia do que está fazendo referência (n
) é capturada. Isso não é diferente de fazer isso:O lambda modifica essa cópia, não o original. É o que você está vendo acontecer nas saídas VS e GCC, conforme o esperado.
A saída do Clang está errada e deve ser relatada como um bug, se ainda não o tiver.
Se você quiser que o seu lambda para modificar
n
, a capturam
por referência em vez disso:[&m]
. Isso não é diferente de atribuir uma referência a outra, por exemplo:Ou, você pode simplesmente se livrar
m
completamente e capturan
por referência em vez disso:[&n]
.Embora, como
n
esteja no escopo global, realmente não precise ser capturado, o lambda pode acessá-lo globalmente sem capturá-lo:fonte
Eu acho que Clang pode estar correto.
De acordo com [lambda.capture] / 11 , uma expressão id usada no lambda refere-se ao membro capturado por cópia do lambda somente se ele constitui um uso de odr . Caso contrário, refere-se à entidade original . Isso se aplica a todas as versões do C ++ desde o C ++ 11.
De acordo com [basic.dev.odr] / 3 do C ++ 17/3, uma variável de referência não é usada odr se a aplicação de conversão lvalue em rvalue produz uma expressão constante.
No rascunho do C ++ 20, no entanto, o requisito para a conversão lvalue em rvalue é descartado e a passagem relevante é alterada várias vezes para incluir ou não a conversão. Consulte a edição 1472 do CWG e a edição 1741 do CWG , bem como a edição 2083 do CWG aberta .
Como
m
é inicializado com uma expressão constante (referindo-se a um objeto de duração de armazenamento estático), usá-lo gera uma expressão constante por exceção em [expr.const] /2.11.1 .Esse não é o caso, no entanto, se as conversões lvalue para rvalue forem aplicadas, porque o valor de
n
não é utilizável em uma expressão constante.Portanto, dependendo se as conversões lvalue para rvalue devem ou não ser aplicadas na determinação do uso de odr, quando você usa
m
o lambda, ele pode ou não se referir ao membro do lambda.Se a conversão for aplicada, o GCC e o MSVC estão corretos, caso contrário, Clang está.
Você pode ver que o Clang altera seu comportamento se você alterar a inicialização de
m
para não ser mais uma expressão constante:Nesse caso, todos os compiladores concordam que a saída é
porque
m
no lambda irá referir-se ao membro do fecho, que é do tipo deint
cópia-inicializada a partir da variável de referênciam
emf
.fonte
m
é odr usada por uma expressão que a nomeia, a menos que aplicar a conversão lvalue em rvalue seja uma expressão constante. Por [expr.const] / (2.7), essa conversão não seria uma expressão constante do núcleo.m += 123;
Aquim
é usado por odr.Isso não é permitido pelo C ++ 17 Standard, mas pode ser por alguns outros rascunhos do Standard. É complicado, por razões não explicadas nesta resposta.
[expr.prim.lambda.capture] / 10 :
O
[m]
meio em que a variávelm
inf
é capturada por cópia. A entidadem
é uma referência ao objeto, portanto, o tipo de fechamento tem um membro cujo tipo é o tipo referenciado. Ou seja, o tipo de membro éint
e nãoint&
.Como o nome
m
dentro do corpo lambda nomeia o membro do objeto de fechamento e não a variávelf
(e essa é a parte questionável), a instruçãom += 123;
modifica esse membro, que é umint
objeto diferente::n
.fonte