C++ 如何在C++;14?

C++ 如何在C++;14?,c++,lambda,c++14,perfect-forwarding,C++,Lambda,C++14,Perfect Forwarding,如何在C++14中编写通用转发lambda 试试看 在函数体中,x是一个左值,因此这不起作用 试试看 添加noexcept是否会使此lambda更适用,从而严格优于#3 您的前两次尝试不起作用,因为返回类型扣除会删除顶级cv限定符和引用,这使得通用转发不可能。您的第三个是完全正确的,只是不必要地冗长,强制转换是隐含在返回类型中的。而noexcept与转发无关 为了完整起见,这里还有一些值得抛弃的选项: auto v0 = [](auto&& x) -> decltype(x

如何在C++14中编写通用转发lambda

试试看 在函数体中,
x
是一个左值,因此这不起作用

试试看
添加
noexcept
是否会使此lambda更适用,从而严格优于#3

您的前两次尝试不起作用,因为返回类型扣除会删除顶级cv限定符和引用,这使得通用转发不可能。您的第三个是完全正确的,只是不必要地冗长,强制转换是隐含在返回类型中的。而
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); };
autov0=[](auto&&x)->decltype(x){return x;};
自动v1=[](自动和&x)->自动和&{return x;};
autov2=[](auto&&x)->auto&&{return std::forward(x);};
autov3=[](auto&&x)->decltype(auto){return std::forward(x);};
v0
将进行编译,看起来好像它返回了正确的类型,但是如果使用右值引用调用它,则编译将失败,因为请求的隐式转换是从左值引用(
x
)到右值引用(
decltype(x)
)。这在gcc和clang上都失败了

v1
将始终返回左值引用。如果你用“临时”来称呼它,那会给你一个悬而未决的参考


v2
v3
都是正确的通用转发lambda。因此,您有三个用于尾部返回类型的选项(
decltype(auto)
auto&
,或
decltype(x)
)和一个用于lambda主体的选项(调用
std::forward
)。

decltype(x)
的返回类型是不够的

可以将按值获取的局部变量和函数参数隐式移动到返回值中,但不能将右值引用获取的函数参数移动到返回值中(x是左值,即使传递右值时,
decltype(x)
==右值引用)。委员会给出的理由是,他们想确定,当编译器隐式移动时,其他人不可能拥有它。这就是为什么我们可以从prvalue(临时的、非引用限定的返回值)和函数本地值移动。然而,有些人可能会做一些愚蠢的事情,比如

std::string str = "Hello, world!";
f(std::move(str));
std::cout << str << '\n';

返回类型
decltype(auto)
在这里也会做同样的事情,但是
auto&
更好地说明了此函数总是返回引用。它还避免了再次提及变量名,我认为这使重命名变量稍微容易一些


从C++17开始,转发lambda在可能的情况下也是隐式的constexpr。

我能生成的最少字符,但功能齐全的版本是:

[](auto&&x)noexcept->auto&&{return decltype(x)(x);}
这使用了一个我觉得很有用的习惯用法——在lambda中转发
auto&
参数时,do
decltype(arg)(arg)
。如果您知道arg是引用类型,则通过
转发
decltype
转发相对来说毫无意义

如果
x
是值类型,
decltype(x)(x)
实际生成
x
的副本,而
std::forward(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);}
templatestruct标记{using type=T;};
templateusing type=typename标记::type;
模板结构R\u t:tag{};
模板结构R\u t:tag{};
模板结构R\u t:tag{};
使用templater=type的模板;
[](auto&&x)noexcept->R{return decltype(x)(x);}

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

尝试1已经足够了。如果数据类型定义了移动构造函数/移动赋值,编译器将为您移动它。这是错误的。OP在问题中指出这将失败是正确的:
x
,因为表达式是左值,如果返回类型是右值引用,则不能用作返回表达式。(即使表达式
x
是左值,
decltype(x)
仍然可以是右值引用类型。)@hvd OP没有指出这一点,但是是的。我认为,如果clang和gcc都做了一些必须纠正的事情,这对我来说是正确的。用删除线更新了答案。OP在问题中指出“在函数体内部,x是一个左值,所以这不起作用。”但不可否认的是,对于
->decltype(x)
版本,这有点令人困惑无论如何,clang确实正确地拒绝了它:
[](auto&&x)->decltype(x){return x;}(0)失败,出现“错误:对类型“int”的右值引用无法绑定到类型“int”的左值”。GCC也会这样做:“错误:无法将'int'左值绑定到'int&'。@hvd-Huh。我当时是如何测试这个的。我想答案是“糟糕”的叮当声没有你描述的bug。如果您实际实例化了模板函数,它将正确地诊断错误。但是您的
静态断言只检查
编译(0)
的签名,错误不在签名中。实际上,GCC也没有错误。如果函数也被实际实例化,那么GCC将正确地拒绝您的示例。有一个密切相关的错误,但不是你的测试程序暴露出来的东西。是的,没有错误。lambda编译后,就不能用右值引用调用它(在gcc或clang上)。我只检查了返回类型,所以没有实际实例化实体。我关闭了错误报告,认为它无效。谢谢你的调查
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); };
std::string str = "Hello, world!";
f(std::move(str));
std::cout << str << '\n';
auto lambda = [](auto && x) noexcept -> auto && { return std::forward<decltype(x)>(x); };
[](auto&&x)noexcept->auto&&{return decltype(x)(x);}
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);}