code

std :: forward는 어떻게 작동합니까?

codestyles 2020. 8. 14. 07:46
반응형

std :: forward는 어떻게 작동합니까? [복제]


중복 가능성 :
forward 사용의 장점

나는 그것이 무엇을하고 그것을 언제 사용 해야하는지 알고 있지만 여전히 그것이 어떻게 작동하는지에 대해 머리를 감쌀 수는 없습니다. std::forward템플릿 인수 추론을 사용하도록 허용 된 경우 가능한 한 자세하게 설명하고 잘못된 경우를 설명하십시오 .

내 혼란의 일부는 이것이다 : "그것은 이름이있는 경우, 그것은 좌변입니다"- 그건 경우 경우 왜 않습니다 std::forward내가 통과 할 때 다르게 행동 thing&& xthing& x?


먼저 std::forward표준에 따라 수행되는 작업을 살펴 ​​보겠습니다 .

§20.2.3 [forward] p2

보고: static_cast<T&&>(t)

( T명시 적으로 지정된 템플릿 매개 변수는 어디에 t있으며 전달 된 인수입니다.)

이제 참조 축소 규칙을 기억하십시오.

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)

( 이 답변 에서 뻔뻔스럽게 도난당했습니다 .)

그런 다음 완벽한 전달을 사용하려는 클래스를 살펴 보겠습니다.

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
};

이제 예제 호출 :

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'!
}

이 단계별 답변이 귀하와 다른 사람들이 std::forward작동 방식을 이해하는 데 도움이되기를 바랍니다 .


std::forwardas에 대한 설명 static_cast<T&&>이 혼란 스럽다고 생각합니다 . 캐스트에 대한 우리의 직관은 유형을 다른 유형으로 변환한다는 것입니다.이 경우에는 rvalue 참조로 변환됩니다. 아니야! 그래서 우리는 또 다른 신비한 것을 사용하여 신비한 것을 설명하고 있습니다. 이 특정 캐스트는 Xeo의 답변 테이블에 정의되어 있습니다. 그러나 질문은 : 왜? 그래서 여기에 내 이해가 있습니다.

Suppose I want to pass you an std::vector<T> v that 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.)

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:

_v = makeAndFillVector();

the compiler would move the 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(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(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 T and 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.

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 T will be somewhat magically deduced differently. If you call it with an lvalue:

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

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

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.

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

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:

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.

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


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

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, forward can simply look at the explicit type T to see what you really passed it. Of course, the exact implementation of doing this is non-trival, if I recall, but that's where the information is.

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

참고URL : https://stackoverflow.com/questions/8526598/how-does-stdforward-work

반응형