Как значение, возвращаемое лямбда-выражением при использовании статической локальной ошибки в MSVC2017 15.9.3 с /std: C++17?
Приведенный ниже пример кода печатает значения из лямбда-функции, которая просто увеличивает и возвращает значение статической переменной локального счетчика.
Это печатает 0,1
а также 2,3
как и ожидалось с gcc и clang с C++17. Но не в Visual Studio Community 2017 15.9.3 с /std:c++17
установить - это печатает 0,0
а также 2,3
вместо.
#include <iostream>
int main() {
auto f = [] {
static int i = 0;
return i++;
};
const int v1 = f(); // Expect v1 = 0
const int v2 = f(); // Expect v2 = 1
// Prints the wrong values (MSVC 15.9.3 with /std:c++17)
std::cout << v1 << "," << v2 << std::endl; // Expect "0,1", prints "0,0"
// Prints the right values (or ought to with C++17 sequencing, anyway)
std::cout << f() << "," << f() << std::endl; // Expect "2,3", prints "2,3"
return 0;
}
Странный вывод (в отладочных сборках x86)
0,0
2,3
Это похоже на ошибку компилятора (поэтому мы подали отчет): https://developercommunity.visualstudio.com/content/problem/347419/unexpected-return-from-lambda-with-static-local-va.html
В чем причина неправильной печати созданной программы? 0
для обоих v1
а также v2
, но правильно печатает 2, 3
после этого? Любое обоснованное предположение о том, что ошибка компилятора?
В качестве обходного пути я использовал вместо этого захват:
auto f = [i = 0]() mutable {
return i++;
};
ОБНОВЛЕНИЕ - как примечание, выходные данные из приведенного выше примера снова отличаются в сборках выпуска x86:
0,1
3,2
Существует еще одна существующая проблема с MSVC, где std::cout
"s <<
оператор не упорядочен слева направо, несмотря на /std:c++17
быть установленным, что я бы предположил результаты в 3,2
по крайней мере, здесь.
1 ответ
MSVC компилирует следующее чисто:
constexpr int foo() {
static int i = 0;
return i++;
}
static_assert(foo() == foo()); // oh no
Это не соответствует стандартам.
Итак, что происходит, так как начиная с C++17, лямбда неявно constexpr
если они могут быть. MSVC ошибочно решает, что лямбда constexpr
и так складывает f()
в постоянную для v2
(что он получил от v1
). Он не делает этого, когда вы выводите его напрямую, потому что он явно не с нетерпением constexpr
такие вещи, как делает gcc (или использует другую эвристику, о которой мы не можем знать).