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&&로 선언한다면, 그 매개변수나 객체는 보편 참조이다.

◾ 선언된 타입의 형태가 정확히 타입&&가 아니거나 타입 추론이 일어나지 않으면 타입&&는 오른값 참조를 뜻한다.

◾ 오른값으로 초기화되는 보편 참조는 오른값 참조에 해당된다. 왼값으로 초기화되는 보편 참조는 왼값 참조에 해당한다.

+ Recent posts