为什么这些 lambda 捕获的值有不同的类型?
Why do these lambda captured values have different types?
两个 int 变量,一个是引用,当被 lambda 捕获时,在 MSVC 的 c++20 中类型不同。为什么?
Scott 在编译时确定类型的技术对 c++14 和 17 产生了预期的结果,但对 c++20 却没有。然而,这种奇怪的差异似乎也出现在早期版本的其他编译中。
具体来说,两个整数,一个是引用,当被值或引用捕获时会产生不同的类型。 int
和 int &
。然而,较早的版本会产生预期的类型。当按值捕获时,两者都被键入 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-
.
中
两个 int 变量,一个是引用,当被 lambda 捕获时,在 MSVC 的 c++20 中类型不同。为什么?
Scott 在编译时确定类型的技术对 c++14 和 17 产生了预期的结果,但对 c++20 却没有。然而,这种奇怪的差异似乎也出现在早期版本的其他编译中。
具体来说,两个整数,一个是引用,当被值或引用捕获时会产生不同的类型。 int
和 int &
。然而,较早的版本会产生预期的类型。当按值捕获时,两者都被键入 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-
.