如何在 C++14 中编写通用转发 lambda?
How to write a generic forwarding lambda in C++14?
如何在 C++14 中编写通用转发 lambda?
尝试#1
[](auto&& x) { return x; }
在函数体内,x
是一个左值,所以这行不通。
尝试#2
[](auto&& x) { return std::forward<decltype(x)>(x); }
这会正确地转发 lambda 内部的引用,但它总是 return 按值 (除非编译器省略了副本)。
尝试#3
[](auto&& x) -> decltype(x) { return std::forward<decltype(x)>(x); }
这个 return 与参数的类型相同(可能 -> auto&&
也可以)并且似乎可以正常工作。
尝试#4
[](auto&& x) noexcept -> decltype(x) { return std::forward<decltype(x)>(x); }
添加 noexcept
是否会使此 lambda 更适用并因此严格优于 #3?
您的前两次尝试没有成功,因为 return 类型推导会删除顶级 cv 限定符和引用,这使得通用转发变得不可能。你的第三个完全正确 只是不必要地冗长,转换隐含在 return 类型 中。而noexcept
与转发无关。
为了完整性,这里还有一些值得放弃的选项:
auto v0 = [](auto&& x) -> decltype(x) { return x; };
auto v1 = [](auto&& x) -> auto&& { return x; };
auto v2 = [](auto&& x) -> auto&& { return std::forward<decltype(x)>(x); };
auto v3 = [](auto&& x) -> decltype(auto) { return std::forward<decltype(x)>(x); };
v0
将编译并看起来好像它 return 是正确的类型,但如果由于请求从左值引用隐式转换而使用右值引用调用它,编译将失败 ( x
) 到右值引用 (decltype(x)
)。这在 gcc 和 clang 上都失败了。
v1
总是 return 左值引用。如果你用临时调用它,那会给你一个悬垂的参考。
v2
和 v3
都是正确的通用转发 lambda。因此,对于尾随的 return 类型(decltype(auto)
、auto&&
或 decltype(x)
),您有三个选项,对于 lambda 的主体(调用 std::forward
).
return 类型的 decltype(x)
不足。
取值的局部变量和函数参数可以隐式移入return值,但右值引用取的函数参数不行(x是左值,即使decltype(x)
==右值如果传递右值,请参考)。委员会给出的理由是,他们希望确定当编译器隐式移动时,没有其他人可能拥有它。这就是为什么我们可以从纯右值(一个临时的、非引用限定的 return 值)和函数局部值移动的原因。然而,有人可能会做一些愚蠢的事情,比如
std::string str = "Hello, world!";
f(std::move(str));
std::cout << str << '\n';
并且委员会不想悄悄地调用不安全的举动,认为他们应该开始更加保守地使用这个新的 "move" 功能。请注意,在 C++20 中,这个问题将得到解决,您只需执行 return x
,它就会做正确的事情。参见 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0527r0.html
对于转发功能,我会尝试让 noexcept
正确。这里很容易,因为我们只是在处理引用(它是无条件的 noexcept
)。否则,您会破坏关心 noexcept
.
的代码
这使得最终理想的转发 lambda 如下所示:
auto lambda = [](auto && x) noexcept -> auto && { return std::forward<decltype(x)>(x); };
return 类型的 decltype(auto)
将在这里做同样的事情,但 auto &&
更好的文档表明此函数始终 return 是一个参考。它还避免再次提及变量名称,我想这会使重命名变量稍微容易一些。
从 C++17 开始,转发 lambda 在可能的情况下也是隐式 constexpr。
我可以生成的最少字符但功能齐全的版本是:
[](auto&&x)noexcept->auto&&{return decltype(x)(x);}
这使用了一个我觉得有用的习惯用法——当在 lambda 中转发 auto&&
参数时,做 decltype(arg)(arg)
。如果您知道 arg 是引用类型,则通过 forward
转发 decltype
相对没有意义。
如果 x
是值类型,decltype(x)(x)
实际上会生成 x
的副本,而 std::forward<decltype(x)>(x)
会生成对 x
的右值引用。所以 decltype(x)(x)
模式在一般情况下不如 forward
有用:但这不是一般情况。
auto&&
将允许返回引用(匹配传入的引用)。可悲的是,引用生命周期扩展不能与上面的代码一起正常工作——我发现将右值引用转发到右值引用通常是错误的解决方案。
template<class T>struct tag{using type=T;};
template<class Tag>using type=typename Tag::type;
template<class T> struct R_t:tag<T>{};
template<class T> struct R_t<T&>:tag<T&>{};
template<class T> struct R_t<T&&>:tag<T>{};
template<class T>using R=type<R_t<T>>;
[](auto&&x)noexcept->R<decltype(x)>{return decltype(x)(x);}
给你那种行为。左值引用成为左值引用,右值引用成为值。
如何在 C++14 中编写通用转发 lambda?
尝试#1
[](auto&& x) { return x; }
在函数体内,x
是一个左值,所以这行不通。
尝试#2
[](auto&& x) { return std::forward<decltype(x)>(x); }
这会正确地转发 lambda 内部的引用,但它总是 return 按值 (除非编译器省略了副本)。
尝试#3
[](auto&& x) -> decltype(x) { return std::forward<decltype(x)>(x); }
这个 return 与参数的类型相同(可能 -> auto&&
也可以)并且似乎可以正常工作。
尝试#4
[](auto&& x) noexcept -> decltype(x) { return std::forward<decltype(x)>(x); }
添加 noexcept
是否会使此 lambda 更适用并因此严格优于 #3?
您的前两次尝试没有成功,因为 return 类型推导会删除顶级 cv 限定符和引用,这使得通用转发变得不可能。你的第三个完全正确 只是不必要地冗长,转换隐含在 return 类型 中。而noexcept
与转发无关。
为了完整性,这里还有一些值得放弃的选项:
auto v0 = [](auto&& x) -> decltype(x) { return x; };
auto v1 = [](auto&& x) -> auto&& { return x; };
auto v2 = [](auto&& x) -> auto&& { return std::forward<decltype(x)>(x); };
auto v3 = [](auto&& x) -> decltype(auto) { return std::forward<decltype(x)>(x); };
v0
将编译并看起来好像它 return 是正确的类型,但如果由于请求从左值引用隐式转换而使用右值引用调用它,编译将失败 ( x
) 到右值引用 (decltype(x)
)。这在 gcc 和 clang 上都失败了。
v1
总是 return 左值引用。如果你用临时调用它,那会给你一个悬垂的参考。
v2
和 v3
都是正确的通用转发 lambda。因此,对于尾随的 return 类型(decltype(auto)
、auto&&
或 decltype(x)
),您有三个选项,对于 lambda 的主体(调用 std::forward
).
return 类型的 decltype(x)
不足。
取值的局部变量和函数参数可以隐式移入return值,但右值引用取的函数参数不行(x是左值,即使decltype(x)
==右值如果传递右值,请参考)。委员会给出的理由是,他们希望确定当编译器隐式移动时,没有其他人可能拥有它。这就是为什么我们可以从纯右值(一个临时的、非引用限定的 return 值)和函数局部值移动的原因。然而,有人可能会做一些愚蠢的事情,比如
std::string str = "Hello, world!";
f(std::move(str));
std::cout << str << '\n';
并且委员会不想悄悄地调用不安全的举动,认为他们应该开始更加保守地使用这个新的 "move" 功能。请注意,在 C++20 中,这个问题将得到解决,您只需执行 return x
,它就会做正确的事情。参见 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0527r0.html
对于转发功能,我会尝试让 noexcept
正确。这里很容易,因为我们只是在处理引用(它是无条件的 noexcept
)。否则,您会破坏关心 noexcept
.
这使得最终理想的转发 lambda 如下所示:
auto lambda = [](auto && x) noexcept -> auto && { return std::forward<decltype(x)>(x); };
return 类型的 decltype(auto)
将在这里做同样的事情,但 auto &&
更好的文档表明此函数始终 return 是一个参考。它还避免再次提及变量名称,我想这会使重命名变量稍微容易一些。
从 C++17 开始,转发 lambda 在可能的情况下也是隐式 constexpr。
我可以生成的最少字符但功能齐全的版本是:
[](auto&&x)noexcept->auto&&{return decltype(x)(x);}
这使用了一个我觉得有用的习惯用法——当在 lambda 中转发 auto&&
参数时,做 decltype(arg)(arg)
。如果您知道 arg 是引用类型,则通过 forward
转发 decltype
相对没有意义。
如果 x
是值类型,decltype(x)(x)
实际上会生成 x
的副本,而 std::forward<decltype(x)>(x)
会生成对 x
的右值引用。所以 decltype(x)(x)
模式在一般情况下不如 forward
有用:但这不是一般情况。
auto&&
将允许返回引用(匹配传入的引用)。可悲的是,引用生命周期扩展不能与上面的代码一起正常工作——我发现将右值引用转发到右值引用通常是错误的解决方案。
template<class T>struct tag{using type=T;};
template<class Tag>using type=typename Tag::type;
template<class T> struct R_t:tag<T>{};
template<class T> struct R_t<T&>:tag<T&>{};
template<class T> struct R_t<T&&>:tag<T>{};
template<class T>using R=type<R_t<T>>;
[](auto&&x)noexcept->R<decltype(x)>{return decltype(x)(x);}
给你那种行为。左值引用成为左值引用,右值引用成为值。