프로그래밍/C/C++

C++ Universal reference & Reference Collapsing Rules

nanze 2022. 1. 7. 13:41
반응형

이전 정리글은

2022.01.07 - [프로그래밍/C/C++] - C++ rvalue reference 우측값 참조 속성

 

C++ rvalue reference 우측값 참조 속성

이전 정리글 2022.01.06 - [프로그래밍/C/C++] - C++ rvalue reference (우측값 참조) move semantics C++ rvalue reference (우측값 참조) move semantics 이전 정리글은 2022.01.05 - [프로그래밍/C/C++] - C++..

nanze.tistory.com

이번 정리 시간은 C++ 에서의 Universal reference 와 Reference Collapsing Rules 에 대해 정리해보자.

Universal reference

유니버설 레퍼런스(universal reference)는 우측값 참조(rvalue reference)가 될 수도 있고 좌측값 참조(lvalue reference)도 될 수 있는 것을 말한다. 그럼 universal reference 는 어떻게 표현될까? 표현식 자체는 우측값 참조(rvalue reference)와 동일한 '&&' 이다. 다른점은 컴파일러에 의해 타입 추론이 발생할 경우 universal reference 가 된다. 해당 경우가 되는 상황을 두 가지정도 알아보자.

 

&& in template argument

템플릿에서 인자 타입에 '&&' 표현식이 쓰면 universal reference 이다. 

template <typename T>
void func(T&& param){
    // do something.
}

int main()
{
    int i = 0;
    func(i);
    func(0);
    
    return 0;
}

위 코드를 보면 func 을 좌측값(lvalue) 과 우측값(rvalue)을 통해서 모두 호출하는데 성공하고 있다. 좌측값을 넘길 경우에는 T 는 T& 타입으로 추론되며 우측값을 넘길 경우에는 T는 T로 추론된다. 그러므로 위 함수를 풀어쓰면 아래와 같다. 

void func(int& param){
    // do something.
}

void func(int&& param){
    // do something.
}

위에서 정리했듯이 타입 추론이 발생해야만 universal reference 이다. 아래 같은 경우는 rvalue reference 이다.

template <typename T>
void func(std::queue<T>&& param);

templae <typename T>
void func2(const T&& param);

위 코드에서 func의 T는 큐 안에 원소에 대한 타입이지 param 의 타입은 큐인 거이다. func2 에서 T 는 universal reference 같지만 앞에 const 가 붙었기 때문에 우측값 참조(rvalue reference)이다. 

 또 한가지 중요한 점은 타입 추론이란 것은 수행 시점에 일어나기에 항상 이점을 유념해야 한다.  아래 코드에서 push_back 함수에서의 T&& 는 우측값 참조(rvalue reference)이다. 왜냐하면 push_back 함수가 호출되는 시점에 T는 이미 타입이 정해져 있기 때문이다. 

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

다음은 rvalueness 와 lvalueness 에 대해서 정리해 보자.  아래 코드를 보자.

template<typename T>
class clsObject
{
    ...
    template <typename Y>
    clsObject(Y&& rhs);      
    ...                     
};

Y는 타입 추론이 발생하기 때문에 Universal reference 이다. 그럼 외부에서 호출 시 rvalue 또는 lvalue를 넘겨 받을 수 있으며 넘겨받으면 rvalue 일 경우에는 rvalueness 를 유지해야 하며 lvalue일 경우에는 lvalueness 를 유지해야 한다. 이러한 역할을 하는 함수가 std::foward() 이다. clsObject 의 rhs 자체는 생성자 내부에서 lvalue 이기에 들어온 원래 값이 rvalue 인지 lvalue 인지 바운드 시키기 위해서 foward() 함수를 사용할 수 있다. 아래 코드도 보도록 해보자.

template<typename T>
class clsObject
{
    ...
    clsObject(clsObject&& rhs);      
    ...                     
};

위 코드에서 우측값 참조를 받는 생성자의 rhs 자체는 lvalue 이기에 내부에서 rvalueness를 바운드시키기 위하여 std::move() 함수를 쓸 수 있다. 이전 글에서 정리했듯이 move는 좌측값(lvalue)를 우측값 참조(rvalue reference)로 타입 캐스팅해준다. 

 

&& in auto

auto 타입 뒤에 쓰이는 '&&' 표현식도 타입 추론이 필요하기에 universal reference 이다.

clsObject&& obj1 = clsObject();
auto&& obj2 = obj1;

위 코드를 확인하면 obj2 는 좌측값 참조(lvalue reference)로 추론되다. 이유는 다들 보이겠지만 obj1 자체는 변수이기에 좌측값(lvalue) 이기 때문이다.

 

Reference Collapsing Rules

Reference Collapsing Rules 은 무엇일까? 타입 추론 발생 시 우측값 참조(rvalue reference) 또는 좌측값 참조(lvalue reference) 인지 결정하는 법칙이라고 생각하면 된다. 앞에서 universal refererence 타입 T&& 의 타입 추론 시 좌측값(lvalue)이 들어오면 T는  T& 로 추론되고 우측값(rvalue)이 들어오면 T로 추론된다고 했다. 예를 들어 int 타입의 값이 들어온다고 했을 경우 다음과 같이 표현될 수 있다. 

template typename<T>
void func(T&& param)
{
}

int main()
{
    int x = 2;
    func(x);
    // lvalue이기 때문에 func(int& && param);   
    func(2);
    // rvalue이기 때문에 func(int&& param);
    return 0;
}

& && 이런 표현식은 존재하지 않는다. 그래서 이런 부분을 축약하는 규칙이 존재하는데 그것이 Reference Collapsing Rules 인 것이다. 

규칙은 다음과 같다. 

&     (lvalue reference) + && (rvalue reference)  =  &
&&  (rvalue reference) + &   (lvalue reference)  =  &
&     (lvalue reference) + &   (lvalue reference)  =  &
&&  (rvalue reference) + && (rvalue reference)  =  &&

좌측값 참조(lvalue reference)가 한개라도 섞이면 좌측값 참조가 된다라고 생각하면 헷갈리지 않을 것이다.

위의 Reference Collapsing Rules 이 적용되는 영역 한 부분을 더보자. 그것은 typedef 이며 다음 코드를 확인하자.

template <typename T>
class clsObject
{
    typedef T& refType;
};

clsObject<int &> obj;

위 코드에서 obj 는 T 로 int& 를 넘기고 있다. 그러면 refType 의 T& 는 int& & 이 되고 이것은 앞선 룰에 따라 int& 이 된다.

 

다음 정리글은

2022.01.08 - [프로그래밍/C/C++] - C++ Perfect forwarding (std::forward 의 역할)

 

C++ Perfect forwarding (std::forward 의 역할)

이전 정리글은 2022.01.07 - [프로그래밍/C/C++] - C++ Universal reference & Reference Collapsing Rules C++ Universal reference & Reference Collapsing Rules 이전 정리글은 2022.01.07 - [프로그래밍/C/C++..

nanze.tistory.com

 

반응형