lambda 如何在 MSVC2017 15.9.3 和 /std:c++17 中使用静态局部错误返回值?

How is value returned by lambda using a static local wrong in MSVC2017 15.9.3 with /std:c++17?

下面的示例代码打印来自 lambda 函数的值,该函数简单地递增 returns 静态局部计数器变量的值。

它按预期使用 gcc 打印 0,12,3,使用 C++17 打印 clang。但在设置了 /std:c++17 的 Visual Studio Community 2017 15.9.3 中不存在 - 它会打印 0,02,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

生成的程序在哪些方面出错,以至于它错误地为 v1v2 打印了 0,但之后正确打印了 2, 3?关于编译器错误是什么的任何有根据的猜测?

作为变通方法,我改为使用捕获:

auto f = [i = 0]() mutable {
    return i++;
};

更新 - 作为旁注,上述示例的输出在 x86 发布版本中再次不同:

0,1
3,2

MSVC 还存在另一个问题,尽管设置了 /std:c++17,但 std::cout<< 运算符未按从左到右的顺序排列,我推测这会导致3,2至少在这里输出。

MSVC 干净地编译了以下内容:

constexpr int foo() {
    static int i = 0;
    return i++;
}
static_assert(foo() == foo()); // oh no

标准。

所以,从 C++17 开始,lambda 表达式隐式 constexpr 如果可以的话。 MSVC 错误地决定 lambda 是 constexpr,因此将 f() 折叠成 v2 的常量(它从 v1 获得)。当你直接输出它时它不会这样做,因为它显然不会像 gcc 那样急切地评估 constexpr 东西(或使用我们不知道的其他一些启发式)。