如何推导 std::move 模板参数
How are std::move template parameters deduced?
假设我们有:
foo(A&& a);
如果你这样做
A a;
foo(a);
它不会编译,抱怨不能将左值绑定到 A&&。 这完全没问题。
但是,鉴于 std::move 的签名,
template<class T> typename remove_reference<T>::type&& std::move(T&& a);
看起来它需要一个右值引用,就像在 foo 中一样,为什么下面的代码符合?
A a;
std::move(a);
a
不是左值吗?
此外,据说编译将实例化:
typename remove_reference<A&>::type&& std::move(A& && a);
我不明白为什么不是:
typename remove_reference<A>::type&& std::move(A && a);
在我看来a
属于A
型,而不是A&
型。
move
不接受右值引用,它采用社区称为通用引用的内容。被类型推导的模板参数的行为与引用折叠规则一致。这意味着:
- 如果
T
是K
,那么T&&
将只是K&&
; - 如果
T
K&
,则T&&
将塌陷到K&
; - 如果
T
K&&
,则T&&
将折叠为T&&
。
这就像&
和&&
的逻辑 AND,其中 &
为 0,&&
为 1:
& &&
|-----------|
& | & | & |
|-----|-----|
&& | & | && |
|-----------|
这就是move
对右值和左值的工作方式。
例子:
template<typename T>
void f(T&&);
f<int> // T is int; plugging int into T makes int&& which is just int&&
f<int&> // T is int&; plugging int& into T is int& && which collapse to int&
f<int&&> // T is int&&; plugging int&& into T is int&& && which collapse to int&&
请注意,引用折叠仅发生在模板参数上;您不能直接键入int&& &&
并期望它进行编译。当然,您不会像那样手动指定类型。这些只是为了显示引用折叠到什么。
所以你真的会这样称呼它:
int i;
f(i); // T is int&; int& && collapses to int&
f(4); // T is int&&; int&& && collapses to int&&
引用折叠也是move
不返回T&&
的原因:如果引用是左值引用,则引用会折叠T
并使move
只返回左值引用。您remove_reference
获取非引用类型,以便&&
真正意味着"右值引用"。
您可以在此处了解更多信息:http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers
在类型演绎的上下文中T&&
的句法形式(包括模板参数推导,但例如也包括声明为 auto
的变量类型的推导)并不表示右值引用,而是 Scott Meyers 所说的 [通用引用]。请注意,只有非常特殊的句法形式T&&
表示通用引用,而其他类似的形式则不被视为通用引用。例如:
template<typename T>
void foo(T&& t); <-- T&& is a universal reference
template<typename T>
void foo(T const&& t); <-- T const&& is NOT a universal reference
template<typename T>
void foo(S<T>&& t); <-- S<T>&& is NOT a universal reference
template<typename T>
struct S { void foo(T&& t); }; <-- T&& is NOT a universal reference
通用引用可以绑定到左值和右值。如果绑定了 A
类型的左值,则T
被推导出为A&
,并且由于引用折叠规则(A& &&
变为A&
),参数的类型解析为 A&
(左值引用)。如果绑定了类型 A
的右值,则推导T
A
,并且参数的类型解析为A&&
(右值引用)。
[注意:引用折叠规则可能看起来很复杂,但它们实际上很容易:引用Stephan T. Lavavej的话,"左值引用是具有传染性的",这意味着当形式T&& &
、T& &
或T& &&
被实例化时,它们总是解析为T&
- 只有形式T&& &&
解析为T&&
这就是为什么当参数为左值时,std::move
函数模板将按如下方式实例化(T
被推导为T&
):
typename remove_reference<A&>::type&& std::move(A& && a);
而当参数是右值时,它将按如下方式实例化(T
被推导出为A
)
typename remove_reference<A>::type&& std::move(A&& a);
不管其他人怎么说,该标准只谈论右值引用。
对于 std::move 如何工作的关键是模板参数推导规则中明确的特殊规则:
[...]如果 [声明的函数参数类型] 是右值引用 到 CV 非限定模板参数,并且参数是左值, 类型"对 A 的左值引用"用于代替 A 表示类型 演绎。[...]
另一部分是引用折叠的规则,它说
如果 [...] 一个类型模板参数 [...] 表示一个类型 TR,它是对 类型 T,尝试创建类型"对 cv TR 的左值引用" 创建类型"对 T 的左值引用",同时尝试创建 类型"对 cv TR 的右值引用"创建类型 TR。
现在template<class T> typename remove_reference<T>::type&& std::move(T&& a);
函数参数 a 匹配上述规则("对 cv-非限定模板参数的右值引用"),因此如果参数是左值,则推导类型将是参数类型的左值引用。在您的情况下,这会导致 T = A&。
将其代入移动收益声明
remove_reference<A&>::type&& std::move<A&>(A& && a);
使用 remove_reference 的定义和引用折叠规则(对 TR => TR 的右值引用),可以得出这样的结果:
A&& std::move<A&>(A& a);
斯科特·迈耶(Scott Meyer)的通用参考概念,正如在其他答案中提出的,是一种有用的方法来记住类型推导规则和参考折叠规则组合的惊人效果:对推导类型的右值引用可能最终成为左值引用(如果该类型可能被推导出为左值引用)。但是该标准没有通用的参考。正如斯科特·迈耶斯(Scott Meyers)所说:这是一个谎言——但一个比真相更有帮助的谎言......
请注意,std::forward 是这个主题的不同转折:它使用额外的间接寻址来防止参数推导(因此必须显式给出类型),但也使用引用折叠将左值作为左值转发,将右值作为右值转发。
- 如何反转整数参数包
- 使用C++库在Android项目中修改gradle中的cmake参数,用于插入指令的测试
- 如何使用默认参数等选择模板专业化
- 模板参数替换失败,并且未完成隐式转换
- 具有默认模板参数的多态类的模板推导失败
- lambda参数转换为constexpr技巧,然后获取带链接的数组
- 将数组作为参数传递给函数安全吗?作为第三方职能部门,可以探索他们想要的之外的其他元素
- 函数调用中参数的顺序重要吗
- 使用std::move将std::unique_ptr作为qt信号参数传递
- 使用 std::move 将参数传递给函数,如果该参数声明为按值传递或使用移动操作数 &&,是否有区别?
- std::move 参数按值传递
- 在 c++11 模式下使用 QtConcurrent::run with move only 参数
- 为什么将临时对象作为参数传递需要 std::move?
- 为什么STD :: MOVE将RVALUE参考作为参数
- 我是否应该对非引用构造函数参数手动调用move
- std::move 或 std::forward 参数 std::unique_ptr&<T>&
- 为什么move返回一个右值引用参数需要用std::move()包装它
- 如何通过move语义移动多个参数
- 如果 std::move 用作 &&&参数的参数,它是否会调用复制运算符?
- 如何推导 std::move 模板参数