为什么这些 lambda 捕获的值有不同的类型?

Why do these lambda captured values have different types?

两个 int 变量,一个是引用,当被 lambda 捕获时,在 MSVC 的 c++20 中类型不同。为什么?

Scott 在编译时确定类型的技术对 c++14 和 17 产生了预期的结果,但对 c++20 却没有。然而,这种奇怪的差异似乎也出现在早期版本的其他编译中。

具体来说,两个整数,一个是引用,当被值或引用捕获时会产生不同的类型。 intint &。然而,较早的版本会产生预期的类型。当按值捕获时,两者都被键入 int。当通过引用捕获时,两者都是 int &.

// Scott Meyer's compile time Type Deduction technique
template<typename T>
class TD;

int main()
{
    int i{ 1 };
    int& ri = i;

    [i, ri]() mutable {
        TD<decltype(i)> WhatAmI1;    // c++14, c++17 TD<int>, c++20 TD<int>
        TD<decltype(ri)> WhatAmI2;   // c++14, c++17 TD<int>, c++20 TD<int &>
    }();

    [&i, &ri]() mutable {
        TD<decltype(i)> WhatAmI3;    // c++14, c++17 TD<int&>, c++20 TD<int>
        TD<decltype(ri)> WhatAmI4;   // c++14, c++17 TD<int&>, c++20 TD<int &>
    }();
}

进一步探索,生成的代码符合预期,并且对于捕获的值和捕获的引用都是相同的。

int main()
{
    int i{ 1 };
    int& ri = i;

    [i, ri]() mutable {
        i += 2;
        ri += 3;
    }();

    [&i, &ri]() {
        i += 2;
        ri += 3;
    }();
}

     [i, ri]() mutable {
         i += 2;
00007FF7F506187F 48 8B 85 E0 00 00 00 mov         rax,qword ptr [this]
00007FF7F5061886 8B 00                mov         eax,dword ptr [rax]
00007FF7F5061888 83 C0 02             add         eax,2
00007FF7F506188B 48 8B 8D E0 00 00 00 mov         rcx,qword ptr [this]
00007FF7F5061892 89 01                mov         dword ptr [rcx],eax
         ri += 3;
00007FF7F5061894 48 8B 85 E0 00 00 00 mov         rax,qword ptr [this]
00007FF7F506189B 8B 40 04             mov         eax,dword ptr [rax+4]
00007FF7F506189E 83 C0 03             add         eax,3
00007FF7F50618A1 48 8B 8D E0 00 00 00 mov         rcx,qword ptr [this]
00007FF7F50618A8 89 41 04             mov         dword ptr [rcx+4],eax



     [&i, &ri]() {
         i += 2;
00007FF7F506190F 48 8B 85 E0 00 00 00 mov         rax,qword ptr [this]
00007FF7F5061916 48 8B 00             mov         rax,qword ptr [rax]
00007FF7F5061919 8B 00                mov         eax,dword ptr [rax]
00007FF7F506191B 83 C0 02             add         eax,2
00007FF7F506191E 48 8B 8D E0 00 00 00 mov         rcx,qword ptr [this]
00007FF7F5061925 48 8B 09             mov         rcx,qword ptr [rcx]
00007FF7F5061928 89 01                mov         dword ptr [rcx],eax
         ri += 3;
00007FF7F506192A 48 8B 85 E0 00 00 00 mov         rax,qword ptr [this]
00007FF7F5061931 48 8B 40 08          mov         rax,qword ptr [rax+8]
00007FF7F5061935 8B 00                mov         eax,dword ptr [rax]
00007FF7F5061937 83 C0 03             add         eax,3
00007FF7F506193A 48 8B 8D E0 00 00 00 mov         rcx,qword ptr [this]
00007FF7F5061941 48 8B 49 08          mov         rcx,qword ptr [rcx+8]
00007FF7F5061945 89 01                mov         dword ptr [rcx],eax
     }();

Link 到编译器资源管理器 https://godbolt.org/z/oe9KPcWWv

Clang、GCC 和 MSVC 在 C++20 模式下的行为对于所有支持 lambda 的标准版本都是正确的。 decltype(i)decltype(ri) 产生命名变量的类型。它没有被重写为引用闭包对象的成员,就像 decltype((ri)) 的情况一样。 (具体参见 C++17 草案 N4659 中的 [expr.prim.lambda.capture]/14 处理此问题)

显然 MSVC 在 C++20 之前的标准模式的默认行为是 non-conforming 如何处理 lambda。根据 documentation 必须给出标志 /Zc:lambda 来处理 lambdas standard-conforming。使用此选项,对于 C++17 和 C++14 模式,MSVC 也会产生与 C++20 模式相同的结果。

例如,参见 this bug report,它作为 not-a-bug 关闭,并附有使用此标志的说明。另请注意,它似乎未包含在 /permissive-.