template<typename T>
void func(T&& param);
템플릿 매개변수 T에는 param으로 전달된 인수가 왼값이었는지 오른값이었는지에 대한 정보가 부호화되어 저장되어있다.
부호화 매커니즘은 간단하다. 왼값 인수가 전달되면 T는 왼값 참조로 추론되고 오른값 인수가 전달되면 T는 비참조 타입으로 추론된다.
Widget widgetFactory(); // 오른값을 돌려주는 함수
Widget w;
func(w); // 왼값으로 호출. Widget&으로 추론
func(widgetFactory()); // 오른값으로 호출. Widget으로 추론
int x;
auto& & rx = x; // 참조에 대한 참조는 선언할 수 없다
C++에서 참조에 대한 참조는 위법이다.
template<typename T>
void func(T&& param);
func(w);
그런데 이런 경우를 한번 보자. 보편 참조를 받는 함수 템플릿에 왼값을 넘겨주면 아마 아래와 같은 형태일 것이다.
void func(Widget& && param);
// void func(Widget& param);
// 실제 만들어지는 함수 형태
참조에 대한 참조는 위법이라고 했으나, 실제로는 참조 축약에 의해 특정 문맥에서는 컴파일러가 참조에 대한 참조를 산출하는 것을 허용한다. 템플릿 인스턴스화는 그런 문맥 중 하나이다.
참조는 왼값과 오른값이 존재하므로 총 4가지이며 아래 규칙에 따라 하나의 참조로 축약된다.
"만일 두 참조 중 하나라도 왼값 참조이면 결과는 왼값 참조이다. 그렇지 않으면 결과는 오른값 참조이다."
위와 같은 참조 축약 덕분에 std::forward가 작동할 수 있다.
template<typename T>
void f(T&& fParam) {
...
someFunc(std::forward<T>(fParam));
}
T&&는 보편 참조이고 전달된 인수가 오른값인 경우 둘 다 오른값이기 때문에 이 경우에만 std::forward가 fParam을 오른값으로 캐스팅하는 것이다.
template<typename T> // C++11
T&& forward(typename remove_reference<T>::type& param) {
return static_cast<T&&>(param);
}
template<typename T> // C++14
T&& forward(remove_reference_t<T>& param) {
return static_cast<T&&>(param);
}
몇 가지 인터페이스가 생략되었지만 forward의 구현은 대략적으로 이렇게 되어있다.
참조 축약은 반환 타입과 캐스팅에도 적용되며 참조 축약 규칙에 의해 인수의 타입이 왼값인 경우 항상 왼값을 반환하게 된다.
참조 축약이 일어나는 문맥은 네가지이고 첫번째 문맥은 가장 흔한 경우이며 이미 다룬 템플릿 인스턴스화이다.
두번째 문맥은 auto 변수에 대한 타입 추론이다. 템플릿 매개변수와 auto의 타입 추론은 본질적으로 같기 때문에 첫번째 경우와 동일하게 적용된다.
◾ 타입 추론에서 왼값과 오른값이 구분된다
◾ 참조 축약이 적용된다
보편 참조는 새로운 종류의 참조가 아니라 위의 두 가지 조건이 만족되는 문맥에서는 오른쪽 참조가 되는 것이다.
세번째 문맥은 typedef와 using의 지정 및 사용이다.
typedef가 지정 또는 평가되는 도중에 참조에 대한 참조가 발생하면 참조 축약이 끼어들어서 참조에 대한 참조를 제거한다.
template<typename T>
class Widget {
public:
typedef T&& RvalueRefToT;
...
};
Widget<int&> w; // int& && RvalueRefToT;
참조에 대한 참조가 발생하여 참조 축약이 끼어들고 왼값 참조가 된다.
이런 경우는 오히려 이름때문에 혼란을 줄 수 있게된다.
마지막 문맥은 decltype 사용이다.
컴파일러가 decltype에 관여하는 타입을 분석하는 도중에 참조에 대한 참조가 발생하면 참조 축약이 끼어든다.
◾ 참조 축약은 템플릿 인스턴스화, auto 타입 추론, typedef와 using의 지정 및 사용, decltype의 지정 및 사용이라는 네 가지 문맥에서 일어난다.
◾ 컴파일러가 참조 축약 문맥에서 참조에 대한 참조를 만들어 내면, 그 결과는 하나의 참조가 된다. 원래의 두 참조 중 하나라도 왼값 참조이면 결과는 왼값 참조이고, 그렇지 않으면 오른값 참조이다.
◾ 타입 추론이 왼값과 오른값을 구분하는 문맥과 참조 축약이 일어나는 문맥에서 보편 참조는 오른값 참조이다.
'도서 > Effective Modern C++' 카테고리의 다른 글
[5장] 항목 30 : 완벽 전달이 실패하는 경우들을 잘 알아두라 (0) | 2023.01.27 |
---|---|
[5장] 항목 29 : 이동 연산이 존재하지 않고, 저렴하지 않고, 적용되지 않는다고 가정하라 (0) | 2023.01.25 |
[5장] 항목 27 : 보편 참조에 대한 오버로딩 대신 사용할 수 있는 기법들을 알아두라 (0) | 2023.01.25 |
[5장] 항목 26 : 보편 참조에 대한 오버로딩을 피하라 (0) | 2023.01.25 |
[5장] 항목 25 : 오른값 참조에는 std::move를, 보편 참조에는 std::forward를 사용하라 (0) | 2023.01.25 |