Я думаю, что Clang действительно может быть правильным.
Согласно [lambda.capture] / 11 , id-выражение, используемое в лямбда -выражении, относится к члену lambda, захваченному при копировании, только если оно представляет собой использование odr . Если это не так, то это относится к исходной сущности . Это относится ко всем версиям C ++, начиная с C ++ 11.
Согласно [basic.dev.odr] / 3 C ++ 17 ссылочная переменная не используется odr, если применение преобразования lvalue-to-rvalue к ней дает константное выражение.
Однако в черновике C ++ 20 требование преобразования lvalue в rvalue отброшено, и соответствующий фрагмент изменялся несколько раз, чтобы включить или не включить преобразование. См РГС вопрос 1472 и РГС выпуск 1741 , а также открыт выпуск РГС 2083 .
Поскольку m
инициализируется с помощью константного выражения (ссылающегося на статический объект длительности хранения), его использование приводит к константному выражению на исключение в [expr.const] /2.11.1 .
Однако это не тот случай, когда применяются преобразования lvalue в rvalue, поскольку значение n
не может использоваться в константном выражении.
Следовательно, в зависимости от того, должны ли преобразования lvalue-to-rvalue применяться при определении использования odr, при использовании m
в лямбде оно может относиться или не относиться к члену лямбды.
Если преобразование должно быть применено, GCC и MSVC верны, в противном случае Clang -.
Вы можете видеть, что Clang меняет m
свое поведение, если вы измените инициализацию, чтобы она больше не была постоянным выражением:
#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;
}
В этом случае все компиляторы соглашаются, что вывод
100 223 100
потому что m
в лямбде будет ссылаться на член замыкания, который имеет тип int
copy-initialized из ссылочной переменной m
в f
.