Как значение, возвращаемое лямбда-выражением при использовании статической локальной ошибки в 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 (или использует другую эвристику, о которой мы не можем знать).

Другие вопросы по тегам