⭐forwarding reference : 보편참조

보물창고·2022년 9월 9일
0

개념 : forward 240803

: 템플릿에 들어오는 타입이 다른 함수로 전달될때 타입의 변경 없이 완벽한 타입을 유지하면서 보내게 하는 것을 perfect foward 완벽한 전달이라고 한다.

  • forwarding 을 사용하기 위해서는 template에 보편참조를 사용해야 한다.

개념 : 보편참조

  • 템플릿에 사용되는 타입 중 하나고, rvalue, lvalue모두 받을 수 있다.
    인수로 보내지는 객체의 타입의 변경없이 원본 타입을 유지하게 한다.
    lvalue 면 그대로 lvalue, rvalue면 그대로 rvalue이다.
    추가적으로는 forward와 함께 사용한다.

보편참조를 통해 함수로 보내지는 객체들의 타입이 변경되지 않는다.

lvalue , rvalue 보냈을 때

forward 까지 사용하는 예제코드

포폴에서 작성한 코드


이전 시간에 배운...

: 레퍼런스 충돌을 통해서

problem

  • 이러한 함수가 있을 때 , 물음표에 들어갈 수 있는 것은?
    lvalue일까? rvalue일까?
    -> 템플릿 인자의 타입을 통해 함수의 인자에 들어갈수 있는 타입이 정해짐!

  • 1번. : 템플릿 인자가 결정될때
    : 템플릿인자가 어떤게 들어오든 간에 ref Collapse로 인해 int&로 결정되기 때문에, 함수의 인수로는 lValue만 들어갈 수 있다.
template <typename T>void f2(T& a) {}
  int main()
  {
  	int n;
  	f2<int>( ??  )   //가)번. f2( int  & a) -> f2(int &a)
  	f2<int&>(  ?? ); //나)번. f2( int& & a) -> f2(int &a)
  	f2<int&&>( ?? )  //다)번. f2( int&& & a) -> f2(int &a)
  }

  • 2번. : 템플릿 인자가 결정되지 않을때 -> 묵시적으로 함수 인자를 보고 타입이 결정된다.
    1. - f2(10) 가능할까? : NO
    : rvalue를 넣는 것을 불가함. // 참조 충돌을 생각하자.
    -> 불가함.
    2. - f2(n)은 가능할까? : OK
    : lvalue 이기 때문에 가능함.
template <typename T>void f2(T& a) {}
  int main()
  {
  	int n;
  	f2(10); // error 발생함. 
    f2(n);
  }

⭐결론

: template에서 템플릿 인자에 참조가 1개 붙어있을 때, 인자는 반드시 lvalue만 들어올 수 있음.

-> 외우는 것이 아니라 참조 충돌 알고 있으면 생각해낼 수 있다.


포워딩 레퍼런스

정의

: template형식에서 함수 인자에 레퍼런스 연산자가 2개로 이루어져 있다면
함수의 인수로 lvalue, rValue 모두 받을 수 있는 참조 방식.
lvalue를 인자로 전달하면 lvalue ref로 결정됨.
rvalue를 인자로 전달하면 rvalue ref로 결정됨.

  • 추가적으로 템플릿인자가 명시적으로 표기할 경우, 표기하지 않을 경우로 구분할 수 있음.

템플릿 인자를 명시할 경우.

-> 함수 인자가 아닌, 템플릿 인자를 통해 함수 결정됨.

template<typename T> void func(T&& a) {}

int main()
{
	int n = 10;
    func<int>(10); // 가) func(int && ) -> func(int&&) 로 결정됨.
                   // 따라서 함수 인자로 rvalue 보내야 함.
	func<int&>(n); // 나) func(int& && ) -> func(int &)
                   // 따라서 함수 인자를 lValue로 보내야 함.
	func<int&&>(10); // 다)func(int&& && ) -> func(int &&) 결정됨
                     // 따라서 함수인자를 rValue로 보내야 함.
    
    // 2개 호출하는 것 이외에도 여러 상황 조합해서 확인해보자. 
    // 함수 어떻게 결정되는지
    //func<int&> (10); //-> error
    // func<int>(n) ;  //-> error
                     

}

템플릿 인자를 명시하지 않을 경우.

-> 함수의 타입에 따라 함수 결정.
lvalue 전달시 T(타입)는 lvalue 참조 로 결정됨.
rvalue 전달시 T(타입)는 rvalue로 결정됨 .

foo(n) => 컴파일러가 보기엔, 함수인자로 lValue가 왔기 때문에, 
       // 템플릿 인자인 T를 int& 로 결정됨.
foo(10) => 컴파일러가 보기엔 , 함수 인자로 rValue가 왔기 때문에, 
       // 템플릿 인자인 T를 int로 결정됨.

문제!

  • 아래의 코드가 주어졌을 때, 함수의 인자로 어떤 타입의 value가
    전달 가능할까???
void f1(int & a){}
void f2(int &&a) {}

template <typename T> void f3(T& a){]
template <typename T> void f4(T&& a){]

\
\
\
\
\
\
\

  • 정답은
    f1은 lvalue
    f2는 rValue
    f3는 lValue
    f4는 rValue , lvalue

  • f4의 경우는 템플릿 타입이 명시될 경우, 템플릿 인자에 따라 함수의 인자 타입이 제한됨. 뭘까용???

    주의할 점.

  • 아래의 코드 Test 클래스는 포워딩 레퍼런스가 아님.

    • 이미 객체를 만들 때, 함수의 인자 타입이 결정되는 코드임.
    • t1을 선언하는 시점에 T는 이미 int로 결정됨.
    • t2를 생성할 때도 이미 goo함수의 인자 타입이 결정됨.
template<typename T> void foo(T&& a) {}

template<typename T> class Test
{
public:
	void goo(T&& a) {} // forwarding reference ????

};

int main()
{
	int n = 10;
	
	foo(n);	// ok
	foo(10);// ok

	Test<int> t1; // 객체 선언이후 바로  void goo(int&& a)함수 타입이 정해짐.
	t1.goo(n);	// error
	t1.goo(10); // ok

	Test<int&> t2; // T int& => void goo( int& && a) => void goo(int& )
	t2.goo(n); // ok
	t2.goo(10); // error
}
  • 템플릿 클래스 안에서 foward reference 함수를 만들고자 한다면 ,
    템플릿 클래스 안에서 템플릿 함수를 만들어야 한다.
    hoo 함수처럼..
template<typename T> class Test
{
public:
	void goo(T&& a) {} // forwarding reference 아님.

	template<typename U> void hoo(U&& a) {} // forwarding reference 
};

auto&&

: 이것도 보편 참조임. 모던 이펙티브 24 참고.

결론

: 보편 참조가 적용되는 것은 template 함수가 호출될때마다 형식 연역이 이루어져야하는 것을 말함.
클래스 템플릿의 경우에는 객체가 생성될때 이미 연역이 이루어지기 때문에 위 처럼 작성해야 함.

vector에서의 push_back은 오버로딩 되어 있고,
emplace_back 은 보편 참조임.

profile
🔥🔥🔥

0개의 댓글