std :: forward를 사용하는 주요 목적은 무엇이며 어떤 문제를 해결합니까?


438

완벽한 전달에서는 std::forward명명 된 rvalue 참조 t1t2명명되지 않은 rvalue 참조 로 변환하는 데 사용됩니다 . 그 목적은 무엇입니까? lvalue로 & 를 inner남겨두면 호출 된 함수에 어떤 영향을 미칩니 까?t1t2

template <typename T1, typename T2>
void outer(T1&& t1, T2&& t2) 
{
    inner(std::forward<T1>(t1), std::forward<T2>(t2));
}

답변:


789

전달 문제를 이해해야합니다. 전체 문제를 자세히 읽을 수 있지만 요약하겠습니다.

기본적으로 expression이 주어지면 표현식 이 동일하기를 E(a, b, ... , c)원합니다 f(a, b, ... , c). C ++ 03에서는 불가능합니다. 많은 시도가 있지만 모두 어떤 측면에서 실패합니다.


가장 간단한 방법은 lvalue-reference를 사용하는 것입니다.

template <typename A, typename B, typename C>
void f(A& a, B& b, C& c)
{
    E(a, b, c);
}

그러나 f(1, 2, 3);lvalue-reference에 바인딩 할 수 없으므로 임시 값을 처리하지 못합니다 .

다음 시도는 다음과 같습니다.

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
    E(a, b, c);
}

위의 문제는 해결하지만 플롭은 뒤집 힙니다. 이제는 E비 const 인수 를 허용하지 않습니다 .

int i = 1, j = 2, k = 3;
void E(int&, int&, int&); f(i, j, k); // oops! E cannot modify these

세 번째 시도는 const를 참조를 받아,하지만 const_cast'는이 s의 const거리 :

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
    E(const_cast<A&>(a), const_cast<B&>(b), const_cast<C&>(c));
}

이것은 모든 값을 받아들이고 모든 값을 전달할 수 있지만 잠재적으로 정의되지 않은 동작으로 이어집니다.

const int i = 1, j = 2, k = 3;
E(int&, int&, int&); f(i, j, k); // ouch! E can modify a const object!

최종 솔루션은 유지 관리가 불가능한 비용으로 모든 것을 올바르게 처리합니다. 당신의 과부하를 제공 f하여, 모든 CONST 및 const가 아닌 조합 :

template <typename A, typename B, typename C>
void f(A& a, B& b, C& c);

template <typename A, typename B, typename C>
void f(const A& a, B& b, C& c);

template <typename A, typename B, typename C>
void f(A& a, const B& b, C& c);

template <typename A, typename B, typename C>
void f(A& a, B& b, const C& c);

template <typename A, typename B, typename C>
void f(const A& a, const B& b, C& c);

template <typename A, typename B, typename C>
void f(const A& a, B& b, const C& c);

template <typename A, typename B, typename C>
void f(A& a, const B& b, const C& c);

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c);

N 개의 주장에는 악몽 인 2 개의 N 조합이 필요합니다 . 이 작업을 자동으로 수행하고 싶습니다.

(이것은 실제로 C ++ 11에서 컴파일러가 수행하는 작업입니다.)


C ++ 11에서는이 문제를 해결할 기회가 있습니다. 한 솔루션은 기존 유형의 템플릿 공제 규칙을 수정하지만 이로 인해 많은 코드가 손상 될 수 있습니다. 따라서 다른 방법을 찾아야합니다.

해결책은 새로 추가 된 rvalue-references 를 대신 사용하는 것입니다 . rvalue-reference 유형을 추론 할 때 새로운 규칙을 도입하고 원하는 결과를 만들 수 있습니다. 결국, 우리는 지금 코드를 깨뜨릴 수 없습니다.

참조에 대한 참조를 제공하는 경우 (참고 참조는 모두 의미 에워싸는 용어 T&T&&, 우리는 결과 유형 알아 내기 위해 다음과 같은 규칙을 사용) :

"유형 T에 대한 참조 인 유형 TR,"cv TR에 대한 lvalue reference "유형을 작성하려는 시도는"lvalue reference to T "유형을 작성하는 반면,"rvalue reference to type cv TR”은 유형 TR을 만듭니다. "

또는 표 형식으로 :

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)

다음으로, 템플릿 인수 추론 : 인수가 lvalue A이면 템플릿 인수에 lvalue 참조를 A에 제공합니다. 그렇지 않으면 정상적으로 추론됩니다. 이것은 소위 범용 참조를 제공합니다 ( 포워딩 참조 는 이제 공식 참조 입니다).

이것이 왜 유용한가요? 결합하여 유형의 값 범주를 추적하는 기능을 유지합니다. lvalue 인 경우 lvalue-reference 매개 변수가 있고 그렇지 않으면 rvalue-reference 매개 변수가 있습니다.

코드에서 :

template <typename T>
void deduce(T&& x); 

int i;
deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
deduce(1); // deduce<int>(int&&)

마지막은 변수의 값 범주를 "전달"하는 것입니다. 함수 내부에서 매개 변수는 lvalue로 전달 될 수 있습니다.

void foo(int&);

template <typename T>
void deduce(T&& x)
{
    foo(x); // fine, foo can refer to x
}

deduce(1); // okay, foo operates on x which has a value of 1

좋지 않습니다. 우리는 우리가 얻은 것과 같은 종류의 가치 범주를 얻어야합니다! 해결책은 다음과 같습니다.

static_cast<T&&>(x);

이것은 무엇을 하는가? 우리가 deduce함수 안에 있고 lvalue가 전달되었다고 가정하십시오. 이는 Ta A&이므로 정적 캐스트의 대상 유형은 A& &&또는 A&입니다. x는 이미 이므로 A&아무 것도하지 않고 lvalue 참조가 남습니다.

rvalue, Tis 가 전달 A되면 정적 캐스트의 대상 유형은 A&&입니다. 캐스트로 인해 rvalue 표현식이 생성 되어 더 이상 lvalue reference로 전달할 수 없습니다 . 매개 변수의 값 범주를 유지했습니다.

이를 종합하면 "완벽한 전달"이됩니다.

template <typename A>
void f(A&& a)
{
    E(static_cast<A&&>(a)); 
}

f좌변를 수신, E좌변을 가져옵니다. 때 f를 rvalue를 수신 E를 rvalue를 가져옵니다. 완전한.


그리고 물론, 우리는 못생긴 것을 제거하고 싶습니다. static_cast<T&&>기이하고 기억하기가 이상합니다. 대신이라는 유틸리티 함수를 만들어 봅시다 forward.

std::forward<A>(a);
// is the same as
static_cast<A&&>(a);

1
f표현식이 아닌 함수가 아니겠습니까 ?
Michael Foukarakis

1
문제의 진술과 관련하여 마지막 시도가 올바르지 않습니다 : const 값을 비 상수로 전달하므로 전혀 전달하지 않습니다. 또한 첫 번째 시도에서는 다음과 const int i같이 승인됩니다. A가 추론됩니다 const int. 실패는 rvalues ​​리터럴에 대한 것입니다. 또한 deduced(1)x 에 대한 호출에 대해서는 x가 int&&아닙니다 int(완전한 전달은 x값을 기준으로하는 매개 변수 인 경우처럼 복사를하지 않습니다 ). 단지 T입니다 int. x전달자에서 lvalue로 평가 되는 이유 는 명명 된 rvalue 참조가 lvalue 표현식이되기 때문입니다.
요하네스 샤 우브-litb

5
forward또는 move여기 에 사용에 차이가 있습니까? 아니면 의미상의 차이입니까?
0x499602D2

28
@David : std::move명시적인 템플릿 인수없이 호출해야하며 항상 rvalue가 발생하지만 결과는 std::forward어느 쪽이든 될 수 있습니다. 사용 std::move이 더 이상 당신을 알지 값을 필요로하고 다른 곳으로 이동하려면, 사용은 std::forward해야 할 일이 함수 템플릿에 전달 된 값에 따라.
GManNickG

5
구체적인 예를 먼저 시작하고 문제를 일으키는 것에 감사드립니다. 매우 도움이되었습니다!
ShreevatsaR

61

std :: forward를 구현하는 개념적 코드가 토론에 추가 될 수 있다고 생각합니다. Scott Meyers 의 효과적인 슬라이드 C ++ 11 / 14 샘플러

std :: forward를 구현하는 개념적 코드

move코드의 기능 은 std::move입니다. 이 대화의 앞부분에 (작동) 구현이 있습니다. libstdc ++ 에서 파일 move.h 에서 std :: forward의 실제 구현을 찾았 지만 전혀 유익하지는 않습니다.

사용자 관점에서 볼 때 그 의미 std::forward는 rvalue 로의 조건부 캐스트입니다. 매개 변수에 lvalue 또는 rvalue가 필요하고 rvalue로 전달 된 경우에만 rvalue로 다른 함수에 전달하려는 함수를 작성하는 경우 유용 할 수 있습니다. std :: forward에서 매개 변수를 줄 바꿈하지 않으면 항상 일반 참조로 전달됩니다.

#include <iostream>
#include <string>
#include <utility>

void overloaded_function(std::string& param) {
  std::cout << "std::string& version" << std::endl;
}
void overloaded_function(std::string&& param) {
  std::cout << "std::string&& version" << std::endl;
}

template<typename T>
void pass_through(T&& param) {
  overloaded_function(std::forward<T>(param));
}

int main() {
  std::string pes;
  pass_through(pes);
  pass_through(std::move(pes));
}

충분히, 그것은 인쇄

std::string& version
std::string&& version

이 코드는 앞에서 언급 한 대화의 예를 기반으로합니다. 시작부터 약 15:00에 슬라이드 10.


2
두 번째 링크가 완전히 다른 곳을 가리키고 있습니다.
Pharap

34

완벽한 전달에서 std :: forward는 명명 된 rvalue 참조 t1 및 t2를 명명되지 않은 rvalue 참조로 변환하는 데 사용됩니다. 그 목적은 무엇입니까? t1 & t2를 lvalue로 남겨두면 호출 된 함수 내부에 어떤 영향을 미칩니 까?

template <typename T1, typename T2> void outer(T1&& t1, T2&& t2) 
{
    inner(std::forward<T1>(t1), std::forward<T2>(t2));
}

표현식에서 명명 된 rvalue 참조를 사용하는 경우 실제로는 lvalue입니다 (이름으로 객체를 참조하기 때문). 다음 예제를 고려하십시오.

void inner(int &,  int &);  // #1
void inner(int &&, int &&); // #2

자, 이렇게 전화 outer하면

outer(17,29);

17과 29는 정수 리터럴이고 rvalue이므로 17과 29를 # 2로 전달하고 싶습니다. 그러나 이후 t1t2표현에서 inner(t1,t2);, 당신은 # 1 대신 2를 호출 할 것 lvalues입니다. 그렇기 때문에 참조를로 명명되지 않은 참조로 되돌려 야 std::forward합니다. 따라서 t1in outer은 항상 lvalue 표현식 인 반면 forward<T1>(t1)에 따라 rvalue 표현식 일 수 있습니다 T1. 후자는 T1lvalue 참조 인 경우 lvalue 표현식 일뿐 입니다. 그리고 T1outer에 대한 첫 번째 인수가 lvalue 표현식 인 경우에만 lvalue 참조로 추정됩니다.


이것은 일종의 간단한 설명이지만 잘 수행되고 기능적인 설명입니다. 사람들은이 답변을 먼저 읽은 다음 원한다면 더 깊이
들어가야

@sellibitze 또 하나의 질문은 int a; f (a)를 추론 할 때 옳은 질문입니다 : "a는 lvalue이므로 int (T &&)는 int (int & &&)"또는 "T &&를 int &와 동일하게 만들기 위해, 그래서 T는 int & "? 나는 후자를 선호합니다.
John

11

t1 & t2를 lvalue로 남겨두면 호출 된 함수 내부에 어떻게 영향을 미칩니 까?

인스턴스화 한 후 T1유형 char이고 T2클래스 인 t1경우 사본 및 참조 t2마다 전달하려고합니다 const. 글쎄, 참조가 inner()아닌 const, 즉 당신이 그렇게하고 싶어하지 않는 한.

outer()rvalue 참조없이 이것을 구현 하는 함수 세트를 작성하여 inner()의 유형 에서 인수를 전달하는 올바른 방법을 추론하십시오 . 나는 당신이 그들 중 2 ^ 2가 필요하고, 주장을 추론하기 위해 꽤 무거운 템플릿 메타 재료가 필요하며, 모든 경우에 이것을 올바르게 할 수있는 많은 시간이 필요하다고 생각합니다.

그리고 누군가가 inner()포인터 당 인수를 취하는 것을 따라옵니다 . 나는 지금 3 ^ 2를 만든다고 생각합니다. (또는 4 ^ 2. 지옥, 나는 const포인터가 변화를 일으킬 지 생각하려고 귀찮게 할 수 없다 .)

그런 다음 5 개의 매개 변수에 대해이 작업을 수행한다고 가정하십시오. 아니면 일곱.

이제 당신은 왜 어떤 밝은 생각들이 "완벽한 전달"을했는지 알 것입니다 : 그것은 컴파일러가 당신을 위해이 모든 것을하게합니다.


5

명확하지 않은 요점 static_cast<T&&>const T&제대로 처리 한다는 것입니다.
프로그램:

#include <iostream>

using namespace std;

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

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

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

template <typename T>
void f(T&& a)
{
    g(static_cast<T&&>(a));
}

int main()
{
    cout << "f(1)\n";
    f(1);
    int a = 2;
    cout << "f(a)\n";
    f(a);
    const int b = 3;
    cout << "f(const b)\n";
    f(b);
    cout << "f(a * b)\n";
    f(a * b);
}

생산 :

f(1)
int&&
f(a)
int&
f(const b)
const int&
f(a * b)
int&&

'f'는 템플릿 함수 여야합니다. 'void f (int && a)'로 정의 된 경우 작동하지 않습니다.


좋은 점은 정적 캐스트의 T &&도 참조 축소 규칙을 따르는 것입니다.
barney

3

forwarding / universal reference가있는 외부 방법과 함께 forward를 사용해야한다는 점을 강조하는 것이 좋습니다. 다음 진술로 자체적으로 앞을 사용하는 것은 허용되지만 혼동을 유발하는 것 외에 다른 것은 아닙니다. 표준위원회는 그러한 유연성을 비활성화하고 싶을 수도 있습니다. 그렇지 않으면 왜 static_cast를 대신 사용하지 않습니까?

     std::forward<int>(1);
     std::forward<std::string>("Hello");

제 생각에, 앞으로 나아가는 것은 r- 값 참조 유형이 도입 된 후 자연스러운 결과 인 디자인 패턴입니다. 잘못된 사용법이 금지되지 않는 한 올바르게 사용되었다고 가정하여 분석법의 이름을 지정해서는 안됩니다.


저는 C ++위원회가 언어 관용구를 "올바르게"사용하거나 "올바른"사용법을 정의해야한다고 생각하지 않습니다 (확실히 지침을 제공 할 수는 있음). 이를 위해 개인의 교사, 상사 및 친구가 서로를 조종해야 할 의무가 있지만 C ++위원회 (및 표준)에는 그러한 의무가 없다고 생각합니다.
SirGuy

예, 방금 N2951을 읽고 표준위원회가 기능 사용과 관련하여 불필요한 제한을 추가 할 의무가 없음에 동의합니다. 그러나이 두 함수 템플릿 (이동 및 전달)의 이름은 라이브러리 파일 또는 표준 문서 (23.2.5 전달 / 이동 헬퍼)에서 해당 정의 만 볼 때 약간 혼란 스럽습니다. 표준의 예제는 개념을 이해하는 데 도움이되지만, 더 명확하게하기 위해 더 많은 설명을 추가하는 것이 유용 할 수 있습니다.
colin dec
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.