C++ std::forward 如何工作?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8526598/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 18:43:10  来源:igfitidea点击:

How does std::forward work?

c++c++11

提问by David

Possible Duplicate:
Advantages of using forward

可能的重复:
使用 forward 的优点

I know what it does and when to use it but I stillcan't wrap my head around how it works. Please be as detailed as possible and explain when std::forwardwould be incorrect if it was allowed to use template argument deduction.

我知道它的作用以及何时使用它,但我仍然无法理解它是如何工作的。请尽可能详细,并解释std::forward如果允许使用模板参数推导什么时候会不正确。

Part of my confusion is this: "If it has a name, it's an lvalue" - if that's the case why does std::forwardbehave differently when I pass thing&& xvs thing& x?

我的部分困惑是这样的:“如果它有一个名字,它就是一个左值”——如果是这样,为什么std::forward当我通过thing&& xvs时表现不同thing& x

回答by Bartosz Milewski

I think the explanation of std::forwardas static_cast<T&&>is confusing. Our intuition for a cast is that it converts a type to some other type -- in this case it would be a conversion to an rvalue reference. It's not! So we are explaining one mysterious thing using another mysterious thing. This particular cast is defined by a table in Xeo's answer. But the question is: Why? So here's my understanding:

我认为std::forwardas的解释static_cast<T&&>令人困惑。我们对强制转换的直觉是它将类型转换为其他类型——在这种情况下,它将转换为右值引用。它不是!所以我们用另一个神秘的东西来解释一个神秘的东西。这个特定的演员表由 Xeo 的答案中的表格定义。但问题是:为什么?所以这是我的理解:

Suppose I want to pass you an std::vector<T> vthat you're supposed to store in your data structure as data member _v. The naive (and safe) solution would be to always copy the vector into its final destination. So if you are doing this through an intermediary function (method), that function should be declared as taking a reference. (If you declare it as taking a vector by value, you'll be performing an additional totally unnecessary copy.)

假设我想传递给你一个std::vector<T> v你应该作为 data member 存储在你的数据结构中的_v。天真(且安全)的解决方案是始终将向量复制到其最终目的地。因此,如果您通过中间函数(方法)执行此操作,则应将该函数声明为引用。(如果您将其声明为按值获取向量,您将执行额外的完全不必要的复制。)

void set(const std::vector<T> & v) { _v = v; }

This is all fine if you have an lvalue in your hand, but what about an rvalue? Suppose that the vector is the result of calling a function makeAndFillVector(). If you performed a direct assignment:

如果你手上有一个左值,这一切都很好,但是一个右值呢?假设向量是调用函数的结果makeAndFillVector()。如果您执行了直接分配:

_v = makeAndFillVector();

the compiler would movethe vector rather than copy it. But if you introduce an intermediary, set(), the information about the rvalue nature of your argument would be lost and a copy would be made.

编译器会移动向量而不是复制它。但是如果你引入了一个中间人,set(),关于你的论点的右值性质的信息将会丢失,并且会被复制。

set(makeAndFillVector()); // set will still make a copy

In order to avoid this copy, you need "perfect forwarding", which would result in optimal code every time. If you're given an lvalue, you want your function to treat it as an lvalue and make a copy. If you're given an rvalue, you want your function to treat it as an rvalue and move it.

为了避免这种复制,您需要“完美转发”,这将导致每次都获得最佳代码。如果给定了一个左值,您希望函数将其视为左值并进行复制。如果给定了一个右值,您希望函数将其视为右值并移动它。

Normally you would do it by overloading the function set()separately for lvalues and rvalues:

通常你会通过set()分别为左值和右值重载函数来实现:

set(const std::vector<T> & lv) { _v = v; }
set(std::vector<T> && rv) { _v = std::move(rv); }

But now imagine that you're writing a template function that accepts Tand calls set()with that T(don't worry about the fact that our set()is only defined for vectors). The trick is that you want this template to call the first version of set()when the template function is instantiated with an lvalue, and the second when it's initialized with an rvalue.

但是现在想象一下,您正在编写一个接受T和调用set()它的模板函数T(不要担心我们set()只为向量定义的事实)。诀窍是您希望此模板在set()使用左值实例化模板函数时调用第一个版本,并在使用右值初始化模板函数时调用第二个版本。

First of all, what should the signature of this function be? The answer is this:

首先,这个函数的签名应该是什么?答案是这样的:

template<class T>
void perfectSet(T && t);

Depending on how you call this template function, the type Twill be somewhat magically deduced differently. If you call it with an lvalue:

根据你如何调用这个模板函数,类型T会有些神奇地以不同的方式推导出来。如果你用左值调用它:

std::vector<T> v;
perfectSet(v);

the vector vwill be passed by reference. But if you call it with an rvalue:

该向量v将通过引用传递。但是如果你用右值调用它:

perfectSet(makeAndFillVector());

the (anonymous) vector will be passed by rvalue reference. So the C++11 magic is purposefully set up in such a way as to preserve the rvalue nature of arguments if possible.

(匿名)向量将通过右值引用传递。因此,C++11 魔法有目的地设置为尽可能保留参数的右值性质。

Now, inside perfectSet, you want to perfectly pass the argument to the correct overload of set(). This is where std::forwardis necessary:

现在,在 PerfectSet 中,您希望将参数完美地传递给set(). 这std::forward是必要的地方:

template<class T>
void perfectSet(T && t) {
    set(std::forward<T>(t));
}

Without std::forward the compiler would have to assume that we want to pass t by reference. To convince yourself that this is true, compare this code:

如果没有 std::forward,编译器将不得不假设我们要通过引用传递 t。要说服自己这是真的,请比较以下代码:

void perfectSet(T && t) {
    set(t);
    set(t); // t still unchanged
}

to this:

对此:

void perfectSet(T && t) {
    set(std::forward<T>(t));
    set(t); // t is now empty
}

If you don't explicitly forward t, the compiler has to defensively assume that you might be accessing t again and chose the lvalue reference version of set. But if you forward t, the compiler will preserve the rvalue-ness of it and the rvalue reference version of set()will be called. This version moves the contents of t, which means that the original becomes empty.

如果您没有明确 forward t,编译器必须防御性地假设您可能再次访问 t 并选择了 set 的左值引用版本。但是如果你 forward t,编译器将保留它的右值性,并且set()将调用的右值引用版本。这个版本移动了 的内容t,这意味着原来的变成了空的。

This answer turned out much longer than what I initially assumed ;-)

这个答案比我最初假设的要长得多;-)

回答by Xeo

First, let's take a look at what std::forwarddoes according to the standard:

首先我们来看看std::forward按照标准是怎么做的:

§20.2.3 [forward] p2

§20.2.3 [forward] p2

Returns:static_cast<T&&>(t)

返回:static_cast<T&&>(t)

(Where Tis the explicitly specified template parameter and tis the passed argument.)

(其中T是显式指定的模板参数,t是传递的参数。)

Now remember the reference collapsing rules:

现在记住参考折叠规则:

TR   R

T&   &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&   && -> T&  // rvalue reference to cv TR -> TR (lvalue reference to T)
T&&  &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&&  && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)

(Shamelessly stolen from this answer.)

(无耻地从这个答案中窃取。)

And then let's take a look at a class that wants to employ perfect forwarding:

然后让我们看看一个想要使用完美转发的类:

template<class T>
struct some_struct{
  T _v;
  template<class U>
  some_struct(U&& v)
    : _v(static_cast<U&&>(v)) {} // perfect forwarding here
                                 // std::forward is just syntactic sugar for this
};

And now an example invocation:

现在是一个示例调用:

int main(){
  some_struct<int> s1(5);
  // in ctor: '5' is rvalue (int&&), so 'U' is deduced as 'int', giving 'int&&'
  // ctor after deduction: 'some_struct(int&& v)' ('U' == 'int')
  // with rvalue reference 'v' bound to rvalue '5'
  // now we 'static_cast' 'v' to 'U&&', giving 'static_cast<int&&>(v)'
  // this just turns 'v' back into an rvalue
  // (named rvalue references, 'v' in this case, are lvalues)
  // huzzah, we forwarded an rvalue to the constructor of '_v'!

  // attention, real magic happens here
  int i = 5;
  some_struct<int> s2(i);
  // in ctor: 'i' is an lvalue ('int&'), so 'U' is deduced as 'int&', giving 'int& &&'
  // applying the reference collapsing rules yields 'int&' (& + && -> &)
  // ctor after deduction and collapsing: 'some_struct(int& v)' ('U' == 'int&')
  // with lvalue reference 'v' bound to lvalue 'i'
  // now we 'static_cast' 'v' to 'U&&', giving 'static_cast<int& &&>(v)'
  // after collapsing rules: 'static_cast<int&>(v)'
  // this is a no-op, 'v' is already 'int&'
  // huzzah, we forwarded an lvalue to the constructor of '_v'!
}

I hope this step-by-step answer helps you and others understand just how std::forwardworks.

我希望这个循序渐进的答案可以帮助您和其他人了解它的std::forward工作原理。

回答by Puppy

It works because when perfect forwarding is invoked, the type T is notthe value type, it may also be a reference type.

它起作用是因为在调用完美转发时,类型 T不是值类型,它也可能是引用类型。

For example:

例如:

template<typename T> void f(T&&);
int main() {
    std::string s;
    f(s); // T is std::string&
    const std::string s2;
    f(s2); // T is a const std::string&
}

As such, forwardcan simply look at the explicit type T to see what you reallypassed it. Of course, the exact implementation of doing this is non-trival, if I recall, but that's where the information is.

因此,forward可以简单地查看显式类型 T 以了解您真正通过它的内容。当然,如果我记得,这样做的确切实现并不重要,但这就是信息所在。

When you refer to a named rvalue reference, then that is indeed an lvalue. However, forwarddetects through the means above that it is actually an rvalue, and correctly returns an rvalue to be forwarded.

当您引用命名的 rvalue 引用时,那确实是一个左值。但是,forward通过上面的方式检测到它实际上是一个右值,并正确返回一个右值进行转发。