완벽한 전달 perfect forwarding
// ex1) wrapper 함수의 예시
vec.push_back(A(1, 2, 3));
// 위와 동일한 작업을 수행한다
// emplace_back은 받은 인자를 정확한 타입으로 전달해주어야 한다
vec.emplace_back(1, 2, 3);
// ex2)
// wrapper 함수
template <typename T>
void wrapper(T u) {
g(u);
}
class A {};
void g(A& a) { std::cout << "좌측값 레퍼런스 호출" << std::endl; }
void g(const A& a) { std::cout << "좌측값 상수 레퍼런스 호출" << std::endl; }
void g(A&& a) { std::cout << "우측값 레퍼런스 호출" << std::endl; }
int main() {
A a;
const A ca;
std::cout << "원본 --------" << std::endl;
g(a);
g(ca);
g(A());
std::cout << "Wrapper -----" << std::endl;
wrapper(a);
wrapper(ca);
wrapper(A());
// 결과:
// 원본 --------
// 좌측값 레퍼런스 호출
// 좌측값 상수 레퍼런스 호출
// 우측값 레퍼런스 호출
// Wrapper -----
// 좌측값 레퍼런스 호출
// 좌측값 레퍼런스 호출
// 좌측값 레퍼런스 호출
}
- 어떤 타입으로 인자를 넘기는 모두 좌측값으로 인식
- C++ 컴파일러가 템플릿 타입을 추론할 때 인자가 T&가 아닌 T 타입이라면 const를 무시하기 때문
// 아래 함수를 추가한다 하더라도 A()는 우측값으로 전달되기 때문에
// 여전히 컴파일 에러를 발생
template <typename T>
void wrapper(T& u) {
g(u);
}
// 따라서 아래와 같이 2개의 함수를 모두 정의해 주면
// 정상적인 컴파일이 진행됨
template <typename T>
void wrapper(T& u) {
std::cout << "T& 로 추론됨" << std::endl;
g(u);
}
template <typename T>
void wrapper(const T& u) {
std::cout << "const T& 로 추론됨" << std::endl;
g(u);
}
// ...
wrapper(a);
wrapper(ca);
wrapper(A());
// 원본 --------
// 좌측값 레퍼런스 호출
// 좌측값 상수 레퍼런스 호출
// 우측값 레퍼런스 호출
// Wrapper -----
// T& 로 추론됨
// 좌측값 레퍼런스 호출
// const T& 로 추론됨
// 좌측값 상수 레퍼런스 호출
// const T& 로 추론됨
// 좌측값 상수 레퍼런스 호출
// A() 의 경우 const T& 로 추론되면서 g(const T&) 함수를 호출
// wrapper 안에 u는 항상 좌측값
// 언제나 좌측값 레퍼런스를 받는 함수들이 오버로딩
- 함수 인자가 늘어나면 모든 조합의 함수를 정의해줘야 함
- 이는 대단히 비효율적
- 일반적인 레퍼런스가 우측값을 받을 수 없기 때문에, 상수 레퍼런스의 조합도 정의해줘야 함
- 인자가 늘어날수록 만들어줘야 하는 함수가 많아짐
- 디폴트로 상수 레퍼런스를 받게 한다면, 상수가 아닌 레퍼런스도 상수로 받게되는 문제점 발생
보편적 레퍼런스 Universal reference
- 템플릿 인자 T 에 대해서 우측값 레퍼런스로 받는 형태
- 그저 우측값만 받는 레퍼런스와는 다름
- 탬플릿 타입의 우측값 레퍼런스는 좌측값 역시 받을 수 있다
// 함수는 인자로 T && 를 받음
template <typename T>
void wrapper(T&& u) {
// 여기에 forward 함수 추가!
g(std::forward<T>(u));
}
class A {};
void g(A& a) { std::cout << "좌측값 레퍼런스 호출" << std::endl; }
void g(const A& a) { std::cout << "좌측값 상수 레퍼런스 호출" << std::endl; }
void g(A&& a) { std::cout << "우측값 레퍼런스 호출" << std::endl; }
int main() {
A a;
const A ca;
std::cout << "원본 --------" << std::endl;
g(a);
g(ca);
g(A());
std::cout << "Wrapper -----" << std::endl;
wrapper(a);
wrapper(ca);
wrapper(A());
// 원본 --------
// 좌측값 레퍼런스 호출
// 좌측값 상수 레퍼런스 호출
// 우측값 레퍼런스 호출
// Wrapper -----
// 좌측값 레퍼런스 호출
// 좌측값 상수 레퍼런스 호출
// 우측값 레퍼런스 호출
}
레퍼런스 겹침 규칙 reference collapsing rule
void show_value(int&& t) { cout << t; }
int main()
{
show_value(3); // 우측값 수용
int x = 3; // 좌측값
show_value(x); // 에러 발생
return 0;
}
- 일반적으로 위와 같은 경우는 에러가 발생
- 하지만 탬플릿 타입에서 우측값 인자를 받는 경우, 아래 규칙에 따라 T 의 타입을 추론
// & 는 1 이고 && 은 0 이라 둔 뒤에, OR 연산
typedef int& T;
T& r1; // int& &; r1 은 int&
T&& r2; // int& &&; r2 는 int&
typedef int&& U;
U& r3; // int&& &; r3 는 int&
U&& r4; // int&& &&; r4 는 int&&
- Wrapper에서 받은 인자가 우측값이고 이를 내부 함수에도 우측값으로 변환해 전달하려면
- move 함수를 호출해야 함
- 받은 인자가 우측값 레퍼런스 일때에만 move 함수를 호출해야 함
- 이 문제를 forward 함수를 이용해 해결 가능
- 인자가 실제 우측값일 경우에만 move를 적용한 것으로 작동
// forward 함수의 내부 모습
// remove_reference는 모든 &를 소거
template <class S>
S&& forward(typename std::remove_reference<S>::type& a) noexcept {
return static_cast<S&&>(a);
}
// S = A& 일 때
A&&& forward(typename std::remove_reference<A&>::type& a) noexcept {
return static_cast<A&&&>(a);
}
A& forward(A& a) noexcept { return static_cast<A&>(a); }
// S = A&& 일 때
A&&&& forward(typename std::remove_reference<A&&>::type& a) noexcept {
return static_cast<A&&&&>(a);
}
A&& forward(A& a) noexcept { return static_cast<A&&>(a); }
출처