如何在 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 左值引用。如果你用临时调用它,那会给你一个悬垂的参考。

v2v3 都是正确的通用转发 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);}

给你那种行为。左值引用成为左值引用,右值引用成为值。