std :: forward如何获得正确的参数?

时间:2015-03-19 01:27:31

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

考虑:

void g(int&);
void g(int&&);

template<class T>
void f(T&& x)
{
    g(std::forward<T>(x));
}

int main()
{
    f(10);
}

由于id-expression x是左值,而std::forward有左值和右值的重载,为什么调用不会绑定到std::forward的重载左值?

template<class T>
constexpr T&& forward(std::remove_reference_t<T>& t) noexcept;

3 个答案:

答案 0 :(得分:16)

绑定到std::forward的重载以获取左值:

template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept;

它与T == int绑定。指定此函数返回:

static_cast<T&&>(t)

因为T中的f推断为int。所以这个重载将左值int强制转换为xvalue:

static_cast<int&&>(t)

因此调用g(int&&)重载。

总之,std::forward的左值重载可能会将其参数转换为左值或右值,具体取决于调用它的T的类型。

std::forward的右值超载只能转换为右值。如果您尝试调用该重载并转换为左值,则程序格式错误(需要编译时错误)。

所以重载1:

template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept;

抓住左撇子。

重载2:

template <class T> constexpr T&& forward(remove_reference_t<T>&& t) noexcept;

捕获rvalues(xvalues和prvalues)。

重载1可以将其左值参数转换为左值或右值(后者将被解释为用于重载解析目的的右值)。

重载2可以将其rvalue参数仅转换为xvalue(为了解决重载问题,它将被解释为rvalue)。

过载2适用于标有&#34; B的情况。应该将右值作为右值转发&#34;在N2951。简而言之,这种情况可以:

std::forward<T>(u.get());

您不确定u.get()是否返回左值或左值,但如果T不是左值引用类型,则您希望移动返回的值。但是你不能使用std::move,因为如果T 左值参考类型,那么你想要从回归。

我知道这听起来有点做作。但是N2951设置了激励用例{em>激励用于std::forward应该如何使用显式提供的模板参数的所有组合以及隐式提供的表达式类别普通参数。

这不容易阅读,但std::forward的每个模板和普通参数组合的基本原理都在N2951。当时这对委员会来说是有争议的,而不是轻易卖出。

std::forward的最终形式并不完全是N2951提出的内容。但是 传递了N2951中提供的所有六个测试。

答案 1 :(得分:4)

  

为什么调用绑定到std::forward的带有左值的重载?

确实如此,但是std::forward does not deduce它的模板参数,你告诉它它是什么类型,以及魔术发生的地方。您已将prval传递给f(),因此f()推断[T = int]。然后它调用forward的左值超载,由于reference collapsingstatic_cast<T&&>内的返回类型和forward都属于int&&类型,因此调用void g(int&&)重载。

如果您要将左值传递给f()

int x = 0;
f(x);

f()推导出[T = int&],再次调用forward的相同左值重载,但这次返回类型和static_cast<T&&>都是int&,因为参考折叠规则。然后,这将调用void g(int&)重载。

Live demo


霍华德已经对你的问题有了一个很好的答案,为什么需要forward的右值超载,但我会添加一个人为的例子来说明这两个版本在起作用。

两个重载背后的基本思想是,在传递给forward的表达式的结果产生rvalue(prvalue或xvalue)的情况下,将调用rvalue重载。

假设您的类型foo具有ref-qualified get()成员函数重载对。具有&&限定符的那个返回int而另一个返回int&

struct foo
{
    int i = 42;
    int  get() && { return i; }
    int& get() &  { return i; }
};

并说f()会调用传递给它的get()成员函数,并将其转发到g()

template<class T>
auto f(T&& t)
{
    std::cout << __PRETTY_FUNCTION__ << '\n';
    g(forward<decltype(forward<T>(t).get())>(forward<T>(t).get()));
}

foo foo1;
f(foo1);   // calls lvalue overload of forward for both calls to forward

f(std::move(foo1)); // calls lvalue overload of forward for forward<T>(t)
                    // but calls rvalue overload for outer call to forward

Live demo

答案 2 :(得分:2)

std :: forward如何接收正确的参数?

为了完美转发,请使用代码:

template<class T>
void f(T&& x)
{
    g(std::forward<T>(x));
}

请注意:std :: forward需要函数参数和模板类型参数。  模板参数T将编码传递给param的参数是左值还是右值,然后转发使用它。 在这种情况下,无论是x是什么,x本身是左值,选择重载1, x是转发(通用)参考参数。规则如下: < / p>

  1. 如果f参数&#39; s expr为左值,则xT都会左值参考 输入
  2. 如果f参数的expr是rvalue,T将是非引用类型。
  3. 例如使用gcc源代码:

      template<typename _Tp>
        constexpr _Tp&&
        forward(typename std::remove_reference<_Tp>::type& __t) noexcept
        { return static_cast<_Tp&&>(__t); } //overload 1
    
    
      template<typename _Tp>
        constexpr _Tp&&
        forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
        {
          static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
                " substituting _Tp is an lvalue reference type");
          return static_cast<_Tp&&>(__t);
        }// overload 2
    
    • 如果类型的传递函数f值为stringstring&,则为 转发函数_Tp将为string&__t将为string & & 等于string&_Tp&&等于string& && string&。(参考折叠)
    • 如果类型的传递函数f值为stringstring&&,则为 转发函数_Tp将为string__t将为string& _Tp&&string&&将为static_assert

    该功能的作用是什么?

    • 如果rvalue为rvalue(Overload 2)或lvalue为lvalue(Overload 1),则无需执行任何操作,只需返回。
    • 如果您不小心或其他原因,请将左值引导至左值,通过std::forward<T>(u.get());为您提供编译错误。(重载2)
    • 如果你做左值(超载1),它与std :: move相同,但不方便。

    正如Scott Meyers所说:

      

    鉴于std :: move和std :: forward都归结为强制转换   唯一的区别是std :: move总是施放,而   std :: forward有时只会,你可能会问我们是否可以   免除std :: move并且只使用std :: forward到处。 来自a   纯粹是技术角度,答案是肯定的:std :: forward可以做到   这一切。 std :: move是没有必要的。当然,这两个功能都不是   真的很必要,因为我们可以随处写演员,但我   希望我们同意,那将是令人讨厌的。

    什么是rvalue重载?

    我不确定,应该在你真正需要它的地方使用它。如Howard Hinnant所说:

      

    过载2适用于标有&#34; B的情况。应该将右值转发为   右值&#34;在N2951。简而言之,这种情况可以:

         

    {{1}}

         

    你不确定u.get()是否返回左值或右值,但是   无论哪种方式,如果T不是左值参考类型,你想要移动   返回值。但是你不能使用std :: move,因为如果T是左值   参考类型,您不想退回。

    总之,它允许我们使用T&#39;类型决定是否移动。