Я думаю, що Кланг насправді може бути правильним.
Згідно з [lambda.capture] / 11 , ідентифікаційний вираз, який використовується в лямбда, відноситься до захопленого під копію лямбда члена, лише якщо він представляє собою стійке використання . Якщо цього немає, то він посилається на оригінальну сутність . Це стосується всіх версій C ++, починаючи з C ++ 11.
Відповідно до [basic.dev.odr] / 3 C ++ 17, опорна змінна не застосовується, якщо застосувати до неї перетворення lvalue в rvalue дає постійний вираз.
Однак у проекті C ++ 20 вимога щодо перетворення значення-в -значення відмінена, і відповідний пасаж змінюється багаторазово, щоб включати або не включати перетворення. Див. Випуск CWG 1472 та випуск CWG 1741 , а також відкритий випуск CWG 2083 .
Оскільки m
ініціалізується постійним виразом (посилається на статичний об'єкт тривалості зберігання), його використання дає постійний вираз за виключенням у [expr.const] /2.11.1 .
Однак це не так, якщо застосовуються конверсії lvalue-to-rvalue, оскільки значення n
не використовується в постійному виразі.
Тому, залежно від того, чи слід застосовувати конверсії lvalue-to-rvalue при визначенні odr-використання, коли ви використовуєте m
в лямбда, він може чи не може стосуватися члена лямбда.
Якщо перетворення слід застосувати, GCC та MSVC є правильними, інакше 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
копіювання, ініціалізований із змінної змінної m
в f
.