Я обнаружил, что результаты в разных компиляторах различны, если я использую лямбду для захвата ссылки на глобальную переменную с изменяемым ключевым словом, а затем изменяю значение в лямбда-функции.
#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;
}
Результат от VS 2015 и GCC (g ++ (Ubuntu 5.4.0-6ubuntu1 ~ 16.04.12) 5.4.0 20160609):
100 223 100
Результат из clang ++ (версия clang 3.8.0-2ubuntu4 (tags / RELEASE_380 / final)):
100 223 223
Почему это происходит? Это разрешено стандартами C ++?
c++
c++11
lambda
language-lawyer
Уилли
источник
источник
Ответы:
Лямбда не может захватить ссылку сама по значению (используйте
std::reference_wrapper
для этой цели).В вашей лямбде
[m]
захватm
по значению (потому что&
в захвате его нет), поэтомуm
(ссылка на негоn
) сначала разыменовывается, и захватывается копия объекта, на который он ссылается (n
). Это ничем не отличается от этого:Затем лямбда изменяет эту копию, а не оригинал. Это то, что вы видите на выходах VS и GCC, как и ожидалось.
Вывод Clang неверен, и его следует сообщить об ошибке, если это еще не сделано.
Если вы хотите , чтобы ваш лямбда изменять
n
, улавливаниеm
по ссылке , а:[&m]
. Это ничем не отличается от присвоения одной ссылки другой, например:Или, вы можете просто избавиться от
m
полностью и захватаn
по ссылке , а:[&n]
.Хотя, поскольку он
n
находится в глобальной области видимости, его вообще не нужно захватывать, лямбда может получить к нему глобальный доступ без захвата:источник
Я думаю, что 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
свое поведение, если вы измените инициализацию, чтобы она больше не была постоянным выражением:В этом случае все компиляторы соглашаются, что вывод
потому что
m
в лямбде будет ссылаться на член замыкания, который имеет типint
copy-initialized из ссылочной переменнойm
вf
.источник
m
используется в выражении с именем odr, если ее применение к преобразованию lvalue-to-rvalue не будет константным выражением. Согласно [expr.const] / (2.7) это преобразование не будет выражением основной константы.m += 123;
Вотm
одр-бЭто не разрешено Стандартом C ++ 17, но некоторыми другими черновиками Стандарта это может быть. Это сложно, по причинам, не объясненным в этом ответе.
[expr.prim.lambda.capture] / 10 :
В
[m]
означает , что переменнаяm
вf
захватывается копией. Сущностьm
является ссылкой на объект, поэтому у типа замыкания есть член, тип которого является ссылочным типом. То есть тип члена естьint
, а нетint&
.Поскольку имя
m
внутри лямбда-тела именует член объекта замыкания, а не переменную вf
(и это сомнительная часть), операторm += 123;
изменяет этот член, который отличаетсяint
от объекта::n
.источник