T&&가 오른값 참조라고 가정하는것은 당연한 것 같지만 안타깝게도 그렇지는 않다.
void f(Widget&& param); // 오른값 참조
Widget&& var1 = Widget(); // 오른값 참조
auto&& var2 = var1; // 오른값 참조 아님
template<typename T>
void f(std::vector<T>& param); // 오른값 참조
template<typename T>
void f(T&& param); // 오른값 참조 아님
T&&에는 두 가지 의미가 있다. 하나는 오른값 참조이고 다른 하나는 오른값 참조 또는 왼값 참조 중 하나라는 것이다.
오른값 참조인 것처럼 보여도 상황에 따라서는 왼값 참조로 동작한다.
이런 이중적인 참조는 const, volatile 객체 또는 둘 다 해당되는 객체에 묶일수도 있다. 거의 모든것에 묶을 수 있는 유연한 참조이고 이를 보편 참조(universal reference)라고 부른다. 거의 항상 std::forward를 사용하기 때문에 전달 참조(forwarding reference)라고 부르기도 한다.
template<typename T>
void f(T&& param);
auto&& var2 = var1;
보편 참조는 보통 타입 추론이 일어나는 템플릿 매개변수나 auto 선언에서 나타난다.
template<typename T>
void f(T&& param);
Widget w;
f(w); // 왼값 전달. param의 타입은 Widget&
f(std::move(w)); // 오른값 전달. param의 타입은 Widget&&
하나의 참조가 보편 참조이려면 반드시 타입 추론이 관여해야 한다(필요조건). 그렇지만 타입 추론이 관여했다고 해서 반드시 보편 참조가 되는것은 아니고(충분조건X) 참조 선언의 형태도 정확해야 한다.
template<typename T>
void f(std::vector<T>&& param); // 오른값 참조. 보편 참조가 아님
template<typename T>
void f(const T&& param); // 오른값 참조. 보편 참조가 아님
param의 타입이 단순히 T&&가 아니라 std::vector<T>&&라면 보편 참조가 될 수 없고 오른값 참조로만 동작한다.
또는 const 한정사 하나만 붙여도 보편 참조가 되지 못한다.
그런데 또 T&&라고 다 보편참조는 아니다.
template<class T, class Allocator = allocator<T>>
class vector { // C++ 표준 벡터
public:
void push_back(T&& x);
...
};
push_back의 매개변수는 보편 참조로 보이지만 push_back은 반드시 구체적으로 인스턴스화된 vector의 일부여야 하기 때문에 타입 추론이 전혀 일어나지 않는다.
std::vector<Widget> v; // Widget 타입으로 인스턴스화
class vector<Widget, allocator<Widget>> {
public:
void push_back(Widget&& x); // 오른값 참조
...
};
Widget 타입으로 인스턴스화 되면서 push_back의 매개변수 T&&가 Widget&& 타입으로 결정되고 여기서 타입 추론은 전혀 관여하지 않는다.
template<class T, class Allocator = allocator<T>>
class vector {
public:
template<class... Args>
void emplace_back(Args&&... args);
...
};
이 경우는 T와 독립적이기 때문에 타입 추론이 적용되어서 Args는 보편 참조가 된다.
T가 Args로 바뀌었을 뿐 형태는 똑같다. 그래서 보편 참조의 형태가 T&&라고 할 뿐이다. 정확히는 타입&&이 보편 참조의 형태이다.
auto역시 보편 참조가 될 수 있다고 했는데 흔히 사용되지는 않고 C++14 이후의 람다식에서는 자주 사용되는 편이다.
auto timeFuncInvocation = [](auto&& func, auto&&... params) { // C++14
std::forward<decltype(func)>(func)(
std::forward<decltype(params)>(params)...
);
};
C++14부터 람다식의 매개변수 타입에 auto를 사용할 수 있게 되면서 보편 참조의 사용이 가능해졌다.
◾ 함수 템플릿 매개변수의 타입의 형태가 T&&이고 T가 추론된다면, 또는 객체를 auto&&로 선언한다면, 그 매개변수나 객체는 보편 참조이다.
◾ 선언된 타입의 형태가 정확히 타입&&가 아니거나 타입 추론이 일어나지 않으면 타입&&는 오른값 참조를 뜻한다.
◾ 오른값으로 초기화되는 보편 참조는 오른값 참조에 해당된다. 왼값으로 초기화되는 보편 참조는 왼값 참조에 해당한다.
'도서 > Effective Modern C++' 카테고리의 다른 글
[5장] 항목 26 : 보편 참조에 대한 오버로딩을 피하라 (0) | 2023.01.25 |
---|---|
[5장] 항목 25 : 오른값 참조에는 std::move를, 보편 참조에는 std::forward를 사용하라 (0) | 2023.01.25 |
[5장] 항목 23 : std::move와 std::forward를 숙지하라 (0) | 2023.01.24 |
[4장] 항목 22 : Pimpl 관용구를 사용할 때에는 특수 멤버 함수들을 구현 파일에서 정의하라 (0) | 2023.01.24 |
[4장] 항목 21 : new를 직접 사용하는 것보다 std::make_unique와 std::make_shared를 선호하라 (0) | 2023.01.20 |