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:
#include <stdio.h>
#include <functional>
int n = 100;
void g() {}
std::function<int()> f()
{
int &m = (g(), 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;
}
Nesse caso, todos os compiladores concordam que a saída é
100 223 100
porque m
no lambda irá referir-se ao membro do fecho, que é do tipo de int
cópia-inicializada a partir da variável de referência m
em f
.