在模板代码中转发引用与const左值引用

Jon*_*anE 8 c++ templates c++11

我最近一直在研究C++中的转发引用,下面简要概述了我目前对该概念的理解.

假设我有一个模板函数,foo它将转发引用转换为类型的单个参数T.

template<typename T>
void foo(T&& arg);
Run Code Online (Sandbox Code Playgroud)

如果我用左值调用此函数,那么T将推断为由于参考折叠规则T&使arg参数成为类型.T&T& && -> T&

如果使用未命名的临时函数(例如函数调用的结果)调用此函数,T则将推断为T使arg参数为类型T&&.

foo但是,里面arg是一个命名参数,所以std::forward如果我想将参数传递给其他函数并仍然保持其值类别,我将需要使用.

template<typename T>
void foo(T&& arg)
{
    bar(std::forward<T>(arg));
}
Run Code Online (Sandbox Code Playgroud)

据我所知,cv-qualifiers不受此转发的影响.这意味着如果我使用命名的const变量调用foo,那么T将推断为const T&,因此类型arg也将const T&归因于引用折叠规则.对于const rvalues T将被推导为const T,因此arg将是类型const T&&.

这也意味着如果我修改arg内部的值,foo如果我将一个const变量传递给它,我将得到编译时错误.

现在我的问题.假设我正在编写容器类,并希望提供一种将对象插入容器的方法.

template<typename T>
class Container
{
public:
    void insert(T&& obj) { storage[size++] = std::forward<T>(obj); }
private:
    T *storage;
    std::size_t size;
    /* ... */
};
Run Code Online (Sandbox Code Playgroud)

通过使insert成员函数获取转发引用,如果事实上传递了临时对象,obj我可以使用它std::forward来利用存储类型的移动赋值运算符.Tinsert

以前,当我对转发引用一无所知时,我会编写这个成员函数,并使用const lvalue引用: void insert(const T& obj).

这样做的缺点是,如果insert传递了临时对象,则此代码不会利用(可能更高效)移动赋值运算符.

假设我没有错过任何东西.

是否有任何理由为插入功能提供两个重载?一个采用const左值参考,一个采用转发参考.

void insert(const T& obj);
void insert(T&& obj);
Run Code Online (Sandbox Code Playgroud)

我问的原因是该方法有两个重载状态的参考文档std::vectorpush_back.

void push_back (const value_type& val);
void push_back (value_type&& val);
Run Code Online (Sandbox Code Playgroud)

为什么需要第一个版本(需要a const value_type&)?

Nir*_*man 6

你必须小心函数模板,而不是类模板的非模板方法。您的成员insert本身不是模板。它是模板类的方法。

Container<int> c;
c.insert(...);
Run Code Online (Sandbox Code Playgroud)

我们可以很容易地看到T第二行没有推导出来,因为它已经固定int在第一行,因为它T是类的模板参数,而不是方法。

类模板的非模板方法,仅在一种方式上与常规方法不同,一旦类被实例化:除非它们被实际调用,否则它们不会被实例化。这很有用,因为它允许模板类使用类型,而只有其中的一些方法才有意义(STL 容器中充满了这样的示例)。

最重要的是,在我上面的示例中,由于T固定为int,因此您的方法变为:

void insert(int&& obj) { storage[size++] = std::forward<int>(obj); }
Run Code Online (Sandbox Code Playgroud)

这根本不是转发引用,而是简单地通过右值引用获取,即它只绑定到右值。这就是为什么您通常会看到诸如 之类的两种重载push_back,一种用于左值,一种用于右值。