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

Posted

技术标签:

【中文标题】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? 【发布时间】:2018-12-06 22:21:46 【问题描述】:

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

它打印0,12,3 与 gcc 和 C++17 的预期一致。但不是在 Visual Studio Community 2017 15.9.3 中设置了/std:c++17 - 它会打印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&lt;&lt; 运算符没有从左到右排序,我推测这会导致3,2 在这里输出,在最少。

【问题讨论】:

你真的认为我们可以解释闭源编译器的内部工作原理吗? 两次输入函数不是更好吗? 真正奇怪的是,如果您在第一行设置断点 cout 自动监视窗口显示 v1v2 分别是 01 查看程序集告诉我们的是,是的,它做错了。 @aschepler 好点,我稍微改述了这个问题。 【参考方案1】:

MSVC 干净地编译以下内容:

constexpr int foo() 
    static int i = 0;
    return i++;

static_assert(foo() == foo()); // oh no

符合标准。

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

【讨论】:

谢谢。我注意到从 v1v2 中删除 const 会使问题消失,如果这样做会使编译器决定不折叠 lambda。 @SamTwidale Jup,它们没有被折叠,因为它们不是const(也不是constexpr)。 我刚刚注意到别的东西,也许这是我的错误还是又是 MSVC?在 x86 版本中,示例在我的 PC 上输出 0,13,2,尽管我认为 std::cout 的 couts 更改为打印v1v2 并通过单独调用对f 进行两次调用,它会正确输出0,12,3 @SamTwidale 即使v1v2const?。但是,是的,这是 MSVC 的一个已知错误。 @SamTwidale 不要这么认为。可能再次与不断折叠有关。

以上是关于lambda 如何在 MSVC2017 15.9.3 中使用 /std:c++17 中的静态局部错误返回值?的主要内容,如果未能解决你的问题,请参考以下文章

如果我的模板类型首先作为 lambda 参数出现,MSVC 会引发一个奇怪的错误

与 GCC/MSVC 中的 lambda 转换构造函数的差异

MSVC++:模板的 static_assert 不会在 lambda 内触发

传入 Lambda 时,Visual Studio 2017 中的哪些扩展可以消除“bool”与“std::function”的歧义?

如何单独提取出MSVC2017编译器并在其他IDE使用

将 MSVC 2017 添加到 Qt