Perfect Forwarding

정의

전달받은 인자를 값, const/volatile(cv) 속성, value category(lvalue/rvalue) 등의 변화 없이 그대로 다른 함수에 전달하는 기법.

필요성

  • 특정 함수(예: hoo)가 rvalue 참조를 인자로 받지만, forwarding 함수(chronometry)에서 이를 받는 순간 lvalue가 되어 호출이 안 됨.
  • 이를 해결하기 위해 캐스팅 또는 forwarding이 필요.

초기 예제

  void foo(int n) {}
void goo(int& r) { r = 20; }
void hoo(int&& r) {}

template<class F, class T>
void chronometry(F f, int& arg) { f(arg); } // lvalue 버전

// hoo를 위해서는 rvalue 버전도 필요

// 1. int&& arg로 받되 static_cast 필요

template<class F, class T>
void chronometry(F f, int&& arg) {
    f(static_cast<int&&>(arg));
}
  

논의

어차피 int&, int&& 지원을 위해 여러 버전이 필요한 것이라면 템플릿으로 자동 생성하자 근데 그러려면 함수의 구현부가 동일해야함

대안

int&버전도 캐스팅을 진행하면 동일. 어차피 컴파일 단계에서 동일한 타입으로 캐스팅이라 생략됨 std::forward(arg) 사용 lvalue를 lvalue로 캐스팅하고, rvalue가 함수로 전달되면서 lvalue로 변경되었던 것을 다시 rvalue로 캐스팅해주는 것

개선: std::forward 사용

  template<class F, class T>
void chronometry(F f, T&& arg) {
    f(std::forward<T>(arg));
}
  
  • T&&: forwarding reference (lvalue/rvalue 모두 받음)
  • std::forward: 전달받은 value category 그대로 전달

가변인자 지원 및 반환값 전달

  template<class F, class... Args>
decltype(auto) chronometry(F&& f, Args&&... args) {
    return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
  
  • decltype(auto): 반환값의 cv/ref 속성 보존
  • std::invoke: 함수 포인터, 멤버 함수 포인터, 함수 객체 모두 호출 가능

예제 사용

  Functor f;
chronometry(f, 10);
chronometry(Functor(), 10);
  

유의사항

  • 포인터 인자일 경우 nullptr 사용. (예: chronometry(f, nullptr))
  • 오버로딩 함수는 명확히 캐스팅 후 전달.
  chronometry(static_cast<void(*)(int, int)>(func), 1, 2);