6

在这样的函数模板中

template <typename T>
void foo(T&& x) {
  bar(std::forward<T>(x));
}

如果用右值引用调用,x内部不是右值引用吗?如果 foo 使用左值引用调用,则无论如何都不需要强制转换,因为它也将是. 也将被推导出为左值引用类型,因此不会改变.foofooxfooTstd::forward<T>x

我进行了一项测试,使用boost::typeindex和不使用std::forward<T>.

#include <iostream>
#include <utility>

#include <boost/type_index.hpp>

using std::cout;
using std::endl;

template <typename T> struct __ { };

template <typename T> struct prt_type { };
template <typename T>
std::ostream& operator<<(std::ostream& os, prt_type<T>) {
  os << "\033[1;35m" << boost::typeindex::type_id<T>().pretty_name()
     << "\033[0m";
  return os;
}

template <typename T>
void foo(T&& x) {
  cout << prt_type<__<T>>{} << endl;
  cout << prt_type<__<decltype(x)>>{} << endl;
  cout << prt_type<__<decltype(std::forward<T>(x))>>{} << endl;
  cout << endl;
}

int main(int argc, char* argv[])
{
  foo(1);

  int i = 2;
  foo (i);

  const int j = 3;
  foo(j);

  foo(std::move(i));

  return 0;
}

g++ -Wall test.cc && ./a.outwithgcc 6.2.0和的输出boost 1.62.0

__<int>
__<int&&>
__<int&&>

__<int&>
__<int&>
__<int&>

__<int const&>
__<int const&>
__<int const&>

__<int>
__<int&&>
__<int&&>

编辑:我找到了这个答案:https ://stackoverflow.com/a/27409428/2640636显然,

只要你给参数一个名字,它就是一个左值。

那么我的问题是,为什么选择这种行为而不是将右值引用保留为右值,即使它们被赋予了名称?在我看来,整个转发考验可以通过这种方式规避。

Edit2:我不是在问什么 std::forward。我在问为什么需要它。

4

4 回答 4

4

x 不是 foo 内的右值引用吗?

不,x是一个右值引用类型的左值foo(它有一个名称和一个地址)。结合参考折叠规则和模板类型推导规则,你会发现你需要std::forward得到正确的参考类型。

基本上,如果你传递给 asx的是一个左值,比如 an int,那么T推论为int&。然后int && &变成int&(由于引用折叠规则),即左值引用。

另一方面,如果你传递一个右值,比如42,那么T推导为int,所以最后你有一个int&&作为 的类型x,即一个右值。基本上就是std::forward这样:转换为T&&结果,就像

static_cast<T&&>(x)

这成为要么T&&T&适当的参考折叠规则。

它的用处在泛型代码中变得显而易见,您可能事先不知道您将获得右值还是左值。如果您不调用std::forward而只调用f(x),那么x始终是左值,因此您将在需要时丢失移动语义,并可能最终得到不必要的副本等。

您可以看到差异的简单示例:

#include <iostream>

struct X
{
    X() = default;
    X(X&&) {std::cout << "Moving...\n";};
    X(const X&) {std::cout << "Copying...\n";}
};

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

template <typename T>
void f2(T&& x)
{
    g(x);
}

template <typename T>
void g(T x)
{ }

int main()
{
    X x;
    std::cout << "with std::forward\n";
    f1(X{}); // moving

    std::cout << "without std::forward\n";
    f2(X{}); // copying
}

Live on Coliru

于 2017-02-18T21:46:58.603 回答
2

真的不希望你的参数被自动移动到调用的函数中。考虑这个函数:

template <typename T>
void foo(T&& x) {
  bar(x);
  baz(x);
  global::y = std::forward<T>(x);
}

现在你真的想要自动移动到bar和空参数到baz.

当前要求您指定是否以及何时移动或转发参数的规则并非偶然。

于 2017-02-18T22:16:57.393 回答
1

我得到完全相同的类型有和没有std::forward<T>

...不?你自己的输出证明你错了:

__<int>    // T
__<int&&>  // decltype(x)
__<int&&>  // std::forward<T>(x)

不使用std::forward<T>ordecltype(x)你会得到int而不是int&&. 这可能会无意中无法“传播右值性” ——x考虑这个例子:

void foo(int&)  { cout << "int&\n"; }
void foo(int&&) { cout << "int&&\n"; }

template <typename T>
void without_forward(T&& x)
{
    foo(x);
//      ^
//  `x` is an lvalue!
}

template <typename T>
void with_forward(T&& x)
{
//  `std::forward` casts `x` to `int&&`.
//      vvvvvvvvvvvvvvvvvv
    foo(std::forward<T>(x));
//                      ^
//          `x` is an lvalue!
}

template <typename T>
void with_decltype_cast(T&& x)
{
// `decltype(x)` is `int&&`. `x` is casted to `int&&`.
//      vvvvvvvvvvv
    foo(decltype(x)(x));
//                  ^
//          `x` is an lvalue!
}

int main()
{
    without_forward(1);    // prints "int&"
    with_forward(1);       // prints "int&&"
    with_decltype_cast(1); // prints "int&&"
}

魔杖盒示例

于 2017-02-18T21:55:27.313 回答
0

x作为 r-value 与x具有 r-value-reference 类型不同。

R-value 是expression的属性,而 r-value-reference 是其type的属性。

如果您实际上尝试将作为 r 值引用的变量传递给函数,则会将其视为左值。是在decltype误导你。试试看

#include <iostream>
#include <typeinfo>
using namespace std;

template<class T> struct wrap { };

template<class T>
void bar(T &&value) { std::cout << " vs. " << typeid(wrap<T>).name() << std::endl; }

template<class T>
void foo(T &&value) { std::cout << typeid(wrap<T>).name(); return bar(value); }

int main()
{
    int i = 1;
    foo(static_cast<int &>(i));
    foo(static_cast<int const &>(i));
    foo(static_cast<int &&>(i));
    foo(static_cast<int const &&>(i));
}

输出:

4wrapIRiE   vs. 4wrapIRiE
4wrapIRKiEvs. 4wrapIRKiE
4wrapIiE     vs. 4wrapIRiE (这些应该匹配!)
4wrapIKiE   vs. 4wrapIRKiE (这些应该匹配!)

于 2017-02-18T22:02:26.653 回答