每个人都可能知道我们使用rvalue-references结合参考折叠规则来构建完美的转发函数,如此
template<typename T>
void f(T&& arg) {
otherfunc(std::forward<T>(arg));
}
f(4);
参考折叠规则类似于
+------+-----+--------+
| T | Use | Result |
|------|--------------|
| X& | T& | X& |
| X& | T&& | X& |
| X&& | T& | X& |
| X&& | T&& | X&& |
+------+-----+--------+
因此,在我的示例f
中,T
是int&&
而T&&
是int&& &&
,它会折叠为int&&
。
我的问题是,如果已将T
推断为int&&
,我们为什么需要这些规则?为什么
template<typename T>
void f(T arg);
f(4);
如果void f(int)
是void f(int&&)
,会变成T
而不是int&&
?如果T
确实是int
而T&&
是int&&
因此void f(int&&)
,那么为什么我们需要参考折叠规则,因为它似乎是它们从未被应用过?这是我从有限的知识中可以看出的唯一两个选项,所以很明显有一条我不知道的规则。
从标准中引用这个引用也是有帮助的。
答案 0 :(得分:1)
在您的示例中,T
为int
,而非int&&
。 void f(T arg)
变体将始终按值接受参数,从而制作副本。当然,这无法完美转发:otherfunc
很可能通过引用获取其参数,避免复制;你甚至可以用一个不可复制的类来调用它。
另一方面,假设您调用f()
传递左值,比如类C
。然后T
为C&
,T&&
变为C& &&
折叠为C&
。这样,完美转发可以保留“左值”,可以这么说。这就是崩溃规则的用途。
考虑:
#include <utility>
#include <iostream>
using namespace std;
class C {
public:
C() : x(0) {}
int x;
private:
C(const C&);
};
void otherfunc(C& c) { c.x = 1; }
template<typename T>
void f(T&& arg) {
otherfunc(std::forward<T>(arg));
}
template<typename T>
void g(T arg) {
otherfunc(std::forward<T>(arg));
}
int main() {
C c;
f(c); // OK
// g(c); // Error: copy constructor inaccessible
cout << c.x; // prints 1
return 0;
}
答案 1 :(得分:1)
我的问题是,如果已经将T推断为int&amp;&amp;&amp;和
,为什么我们需要这些规则?
这不是真的。类型推导的规则不会推断该参数是参考。也就是说,在:
template <typename T>
void f(T);
表达式:
X g();
X& h();
X a;
f(g()); // argument is an rvalue, cannot be bound by lvalue-ref
f(h()); // argument is an lvalue
f(a); // argument is an lvalue
在最后两种情况下推导出的类型将是X
,并且在第一种情况下将无法编译。推导出的类型将是值类型,而不是引用类型。
下一步是弄清楚如果模板通过左值或右值参考获取参数,推导出的类型是什么。对于左值引用,选项很明确,修改后的f
:
template <typename T>
void f(T &);
f(g()); // only const& can bind an rvalue: f(const X&), T == const int
f(h()); // f(X&)
f(a); // f(X&)
到目前为止,它已在先前版本的标准中定义。现在的问题是,如果模板采用rvalue-references,推导出的类型应该是什么。这是C ++ 11中添加的内容。现在考虑一下:
template <typename T>
void f(T &&);
并且rvalue只会绑定到右值,而不会绑定到左值。这意味着使用与lvalue-references相同的简单规则(T将使调用编译的类型)第二次和第三次调用不编译:
f(g()); // Fine, and rvalue-reference binds the rvalue
f(h()); // an rvalue-reference cannot bind an lvalue!
f(a); // an rvalue-reference cannot bind an lvalue!
如果没有引用折叠规则,用户必须为模板提供两个重载,一个采用rvalue-reference,另一个采用左值引用。问题在于,随着参数数量的增加,替代方案的数量呈指数级增长,并且在C ++ 03中实现完美转发几乎同样难以实现(唯一的优势是能够使用rvalue-reference检测rvalue)。 / p>
因此需要做一些不同的事情,那就是引用折叠,这实际上是描述所需语义的一种方式。描述它们的另一种方式是,当您通过模板参数键入&&
时,您并不真正要求 rvalue-reference ,因为这不允许使用 lvalue ,但您要求编译器为您提供最佳类型的参考匹配。