从 C ++模板:完整指南(第2版)中考虑此引用:
protected $hidden = ['password', 'remember_token'];
请注意,使用
decltype(auto) ret{std::invoke(std::forward<Callable>(op), std::forward<Args>(args)...)}; ... return ret;
声明ret
不正确。作为一个 引用,auto&&
延长返回值的生命周期,直到 范围的结尾但不超出auto&&
语句的范围 函数的调用者。
作者说return
不适合完美转发返回值。但是,auto&&
是否也形成对xvalue / lvalue的引用?
IMO,decltype(auto)
然后遭受同样的问题。那么,作者的观点是什么?
修改
上面的代码片段应该放在这个函数模板中。
decltype(auto)
答案 0 :(得分:5)
这里有两个扣除。一个来自返回表达式,一个来自std::invoke
表达式。因为decltype(auto)
is deduced to be the declared type for unparenthesized id-expression,我们可以专注于std::invoke
表达式的推论。
引自[dcl.type.auto.deduct] paragraph 5:
如果占位符是
decltype(auto)
类型说明符,则T
应仅为占位符。T
推导出的类型按照[dcl.type.simple]中的描述确定,好像e
是decltype
的操作数。
引自[dcl.type.simple] paragraph 4:
对于表达式
e
,decltype(e)
表示的类型定义如下:
如果
e
是未命名的id-expression,命名结构化绑定([dcl.struct.bind]),decltype(e)
是结构化绑定规范中给出的引用类型声明;否则,如果
e
是未加密码的 id-expression 或未加密码的类成员访问,decltype(e)
是{{1}命名的实体的类型}}。如果没有这样的实体,或者e
命名了一组重载函数,则该程序格式不正确;否则,如果
e
是xvalue,则e
为decltype(e)
,T&&
的类型为T
;否则,如果
e
是左值,则e
为decltype(e)
,T&
的类型为T
;否则,
e
是decltype(e)
的类型。
如果e
是prvalue,则注意decltype(e)
被推断为T
而不是T&&
。这与e
不同。
因此,如果auto&&
是prvalue,则std::invoke(std::forward<Callable>(op), std::forward<Args>(args)...)
的返回类型不是引用,即按值返回,Callable
被推断为相同类型而不是引用,它完美地转发了按值返回的语义。
答案 1 :(得分:4)
但是,decltype(auto)是否也形成对xvalue / lvalue的引用?
没有
部分decltype(auto)
的神奇之处在于它知道ret
是左值,so it will not form a reference。
如果您编写了return (ret)
,它确实已经解析为引用类型,并且您将返回对局部变量的引用。
tl; dr:decltype(auto)
并不总是与auto&&
相同。
答案 2 :(得分:0)
auto&&
始终是引用类型。另一方面,decltype(auto)
可以是引用或值类型,具体取决于所使用的初始化程序。
由于 如果 如上所述,ret
语句中的return
没有用括号括起来,因此call()
的推导返回类型仅取决于的声明类型 > entity ret
,表达式 ret
的值类别上不:>
template<typename Callable, typename... Args>
decltype(auto) call(Callable&& op, Args&&... args) {
decltype(auto) ret{std::invoke(std::forward<Callable>(op),
std::forward<Args>(args)...)};
...
return ret;
}
Callable
按值返回 ,则op
的调用表达式的值类别将是 prvalue 强>。在这种情况下:
decltype(auto)
将推论res
作为非引用类型(即值类型)。 auto&&
会得出res
作为参考类型。decltype(auto)
处的call()
的返回类型与res
的返回类型相同。因此,如果使用auto&&
来推导res
的类型而不是decltype(auto)
,则call()
的返回类型将是对本地对象{{ 1}},在ret
返回后不存在。
答案 3 :(得分:0)
我有一个类似的问题,但特定于如何正确返回ret
,就像我们直接调用invoke
而不是call
一样。
在您显示的示例中,call(A, B)
和std::invoke(A, B)
的{{1}} 没有具有相同的返回类型A
。
具体来说,当B
返回invoke
时,T&&
返回call
。
您可以在此示例中看到它(wandbox link)
T&
因此看来,正确转发函数返回值的唯一方法是这样做
#include <type_traits>
#include <iostream>
struct PlainReturn {
template<class F, class Arg>
decltype(auto) operator()(F&& f, Arg&& arg) {
decltype(auto) ret = std::forward<F>(f)(std::forward<Arg>(arg));
return ret;
}
};
struct ForwardReturn {
template<class F, class Arg>
decltype(auto) operator()(F&& f, Arg&& arg) {
decltype(auto) ret = std::forward<F>(f)(std::forward<Arg>(arg));
return std::forward<decltype(ret)>(ret);
}
};
struct IfConstexprReturn {
template<class F, class Arg>
decltype(auto) operator()(F&& f, Arg&& arg) {
decltype(auto) ret = std::forward<F>(f)(std::forward<Arg>(arg));
if constexpr(std::is_rvalue_reference_v<decltype(ret)>) {
return std::move(ret);
} else {
return ret;
}
}
};
template<class Impl>
void test_impl(Impl impl) {
static_assert(std::is_same_v<int, decltype(impl([](int) -> int {return 1;}, 1))>, "Should return int if F returns int");
int i = 1;
static_assert(std::is_same_v<int&, decltype(impl([](int& i) -> int& {return i;}, i))>, "Should return int& if F returns int&");
static_assert(std::is_same_v<int&&, decltype(impl([](int&& i) -> int&& { return std::move(i);}, 1))>, "Should return int&& if F returns int&&");
}
int main() {
test_impl(PlainReturn{}); // Third assert fails: returns int& instead
test_impl(ForwardReturn{}); // First assert fails: returns int& instead
test_impl(IfConstexprReturn{}); // Ok
}
这是一个陷阱(我掉进去发现了!)
返回decltype(auto) operator()(F&& f, Arg&& arg) {
decltype(auto) ret = std::forward<F>(f)(std::forward<Arg>(arg));
if constexpr(std::is_rvalue_reference_v<decltype(ret)>) {
return std::move(ret);
} else {
return ret;
}
}
的函数非常少见,很容易在一段时间内未被发现。