Item 24. universal reference와 rvalue reference를 구별해라

lvalue를 참조하는 lvalue reference는 T&로 표현되고 rvalue를 참조하는 rvalue reference는 T&&로 표현된다.

그럼 T&&로 표현되면 모두 rvalue reference일까?

정답은 ‘아니오’이다.

T&&는 rvalue reference뿐만 아니라 universal reference를 표현할 때도 사용한다.

그럼 universal reference에 대해 알아보자.


universal reference

universal reference는 참조하는 객체가 lvalue면 lvalue reference가 되고 rvalue면 rvalue reference가 되는 유연성을 가진 reference이다.

universal reference를 사용하는 대표적인 예시로는 템플릿 함수의 매개변수와 auto 선언이 있다.

// 템플릿 함수의 매개변수로 사용
template <typename T>
void f(T&& param);  // universal reference

// auto 선언에서 사용
auto&& var2 = var1; // universal reference

universal reference는 reference이므로 반드시 초기화가 되어야 한다.

이 초기화를 통해 universal reference가 lvalue reference가 될지 rvalue reference가 될지 결정한다.

template <typename T>
void f(T&& param); // universal refernce

Widget w;

// lvalue 인자 전달, param 타입은 Widget& (lvalue reference)
f(w);

// rvalue 인자 전달, param 타입은 Widget&& (rvalue reference)
f(std::move(w));


universal reference의 조건

universal reference가 되기 위한 두 가지 조건은 다음과 같다.

타입 추론이 일어나야 함

universal reference가 되기 위해서는 타입 추론이 일어나야 한다.

다음과 같이 T&& 형태여도 타입 추론이 일어나지 않으면 rvalue reference이다.

void f(Widget&& param);   // 타입 추론 없음, rvalue reference

Widget&& var1 = Widget(); // 타입 추론 없음, rvalue reference

템플릿 클래스 안에 있는 함수들은 특히 조심해야 한다.

타입 추론이 일어난다고 착각할 수 있기 때문이다.

template <class T, class Allocator = allocator<T>>
class vector {
public:
  void push_back(T&& x);
};

std::vector::push_back 멤버 함수는 타입 추론이 일어나지 않는다.

std::vector의 템플릿 인스턴스화 과정에서 이미 타입이 결정되기 때문이다.

std::vector<Widget> v; // 템플릿 인스턴스 생성

// 인스턴스화된 vector
class vector<Widget, allocator<Widget>> {
public:
  void push_back(Widget&& x); // rvalue reference
};

보다시피 std::vector::push_back 멤버 함수는 타입을 추론하지 않기 때문에 매개변수 타입이 rvalue reference이다.

반면 비슷한 개념을 가진 std::vector::emplace_back 멤버 함수는 타입 추론이 일어난다.

template <class T, class Allocator = allocator<T>>
class vector {
public:
  template <class... Args>
  void emplace_back(Args&&... args); // universal reference
};

std::vector::emplace_back 멤버 함수는 매개변수 타입을 추론하므로 universal reference이다.

이처럼 함수를 호출할 때마다 타입 추론이 일어나야 universal reference라고 할 수 있다.

T&& 형태여야 함

universal reference가 되기 위해서는 T&& 형태여야 한다.

타입 추론이 일어나도 T&& 형태가 아니면 rvalue reference이다.

template <typename T>
void f(std::vector<T>&& param); // rvalue reference

std::vector<int> v;
f(v); // 컴파일 에러, lvalue를 rvalue reference가 참조할 수 없음

또한 const가 붙어도 rvalue reference이다.

template <typename T>
void f(const T&& param); // rvalue reference

auto&&T&& 형태로 universal reference이다.

C++14에서는 람다의 매개변수로 universal reference인 auto&&를 사용할 수 있다.

auto timeFuncInvocation = 
  [](auto&& func, auto&&.. params) {
    std::forward<decltype(func)>(func)(
      std::forward<decltype(params)>(params)...
    );
  };


카테고리:

업데이트: