1

大家可能都知道,我们使用右值引用结合引用折叠规则来构建完美的转发函数,像这样

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中,Tisint&&T&&isint&& &&折叠为int&&.

T我的问题是,如果已经推导出来,为什么我们需要这些规则int&&?为什么会

template<typename T>
void f(T arg);

f(4);

变成void f(int)而不是void f(int&&)if Tis int&&? 如果Tis really intand T&&is 是什么使它成为int&&and 因此void f(int&&),那么为什么我们需要参考折叠规则,因为它们似乎从未应用过?根据我有限的知识,我只能说出这两个选项,所以显然有一条我不知道的规则。

查看标准中关于此的引用也会有所帮助。

4

2 回答 2

1

在您的示例中,Tint,不是int&&void f(T arg)变体总是按值接受参数,制作副本。当然,这违背了完美转发的要点:otherfunc很可能通过引用获取其参数,避免复制;你甚至可以用一个不可复制的类来调用它。

另一方面,假设您调用f()传递左值,例如 class C。然后TC&,并且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;
}
于 2013-10-05T03:50:50.987 回答
1

我的问题是,如果 T 已经推导出为 int&&,为什么我们需要这些规则?

这并不完全正确。类型推导的规则不会将参数推导出为引用。也就是说,在:

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&)

到目前为止,它已经在标准的先前版本中定义。现在的问题是,如果模板采用右值引用,那么推导的类型应该是什么。这是 C++11 中添加的内容。现在考虑:

template <typename T>
void f(T &&);

并且右值只会绑定到右值,而永远不会绑定到左值。这意味着使用与左值引用相同的简单规则(什么类型的 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!

如果没有引用折叠规则,用户将不得不为模板提供两个重载,一个采用右值引用,另一个采用左值引用。问题在于,随着参数数量的增加,备选方案的数量呈指数增长,并且在 C++03 中实现完美转发变得几乎一样困难(唯一的优势是能够使用右值引用检测右值)。

所以需要做一些不同的事情,那就是引用折叠,这实际上是描述所需语义的一种方式。描述它们的另一种方式是,当您&&通过模板参数键入时,您并没有真正要求rvalue-reference,因为这不允许使用lvalue进行调用,而是要求编译器给您最好的参考匹配类型。

于 2013-10-05T03:52:45.733 回答