【问题标题】:rvalue template argument implicitly used as lvalue, and std::forwarding working右值模板参数隐式用作左值,并且 std::forwarding 工作
【发布时间】:2012-04-30 20:12:55
【问题描述】:

This examplestd::forward 的用法让我很困惑。这是我编辑的版本:

#include <iostream>
#include <memory>
#include <utility>
using namespace std;

struct A{
    A(int&& n) { cout << "rvalue overload, n=" << n << "\n"; }
    A(int& n)  { cout << "lvalue overload, n=" << n << "\n"; }
};

template<typename> void template_type_dumper();

template<class T, class U>
unique_ptr<T> make_unique(U&& u){
    //Have a "fingerprint" of what function is being called
    static int dummyvar;
    cout<<"address of make_unique::dummyvar: "<<&dummyvar<<endl;
    //g++ dumps two warnings here, which reveal what exact type is passed as template parameter
    template_type_dumper<decltype(u)>;
    template_type_dumper<U>;

    return unique_ptr<T>(new T(forward<U>(u)));
}

int main()
{
    unique_ptr<A> p1 = make_unique<A>(2); // rvalue
    int i = 1;
    unique_ptr<A> p2 = make_unique<A>(i); // lvalue
}

输出是

address of make_unique::dummyvar: 0x6021a4
rvalue overload, n=2
address of make_unique::dummyvar: 0x6021a8
lvalue overload, n=1

关于引用 template_type_dumper 的警告显示,在第一个实例化中,decltype(u) = int&amp;&amp;U = int,第二个实例化为 decltype(u) = int&amp;U = int&amp;

很明显,正如预期的那样,有两种不同的实例化,但她是我的问题:

  1. std::forward 怎么能在这里工作?在第一个实例化中,它的模板参数显式为U = int,它怎么知道它必须返回一个右值引用?如果我改为指定 U&amp;&amp; 会发生什么?
  2. make_unique 被声明为采用右值引用。为什么u 可以是左值引用?有什么我遗漏的特殊规则吗?

【问题讨论】:

标签: c++ templates c++11 rvalue-reference perfect-forwarding


【解决方案1】:

make_unique 被声明为采用右值引用。你怎么能成为左值引用?有什么我遗漏的特殊规则吗?

make_unique 被声明为引用。需要推断出该参考是什么类型的。如果传递了foo 类型的左值,则U 被推断为foo&amp; 并且U&amp;&amp; 变为foo&amp;,因为引用折叠规则(基本上,将左值引用与另一个引用“组合”总是会产生左值引用;组合两个右值引用会产生一个右值引用)。如果传递了foo 类型的右值,则推导出Ufoo 并且U&amp;&amp;foo&amp;&amp;

这是支持完美转发的因素之一:使用U&amp;&amp;,您可以同时获取左值和右值,并推导出U 以匹配适当的值类别。然后使用std::forward,您可以转发保留相同值类别的值:在第一种情况下,您会得到转发左值的std::forward&lt;foo&amp;&gt;,在第二种情况下,您会得到转发右值的std::forward&lt;foo&gt;

在第一次实例化中,它的模板参数显式为U = int,它怎么知道它必须返回一个右值引用?

因为std::forward&lt;T&gt; 的返回类型始终是T&amp;&amp;。如果您通过int,它将返回int&amp;&amp;。如果你通过int&amp;,它会再次返回int&amp;,因为引用折叠规则。

如果我改为指定 U&& 会发生什么?

您将拥有std::forward&lt;int&amp;&amp;&gt;,并且引用折叠规则使int&amp;&amp; &amp;&amp; 仍然是右值引用:int&amp;&amp;

【讨论】:

  • 遗憾的是,大多数在线文章在解释右值引用时似乎更多地关注移动语义,而不是这种引用折叠机制,它基本上回答了我所有的问题。
  • U 是一种类型,因此它实际上没有“值类别”。相反,U 被推断为非引用或左值引用类型。
  • @KerrekSB 我修正了措辞 :)
  • @LorenzoPistone:移动语义是最常用的。引用折叠等描述起来要复杂得多,并且仅在您编写模板转发功能时才有用。这不是每个人都会做的事情。
猜你喜欢
  • 1970-01-01
  • 2013-06-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-24
  • 1970-01-01
相关资源
最近更新 更多