ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Effective Modern C++] 참조 축약 (Reference collapsing)
    C++/Effective Modern C++ 2022. 8. 10. 13:47

    참조에 대한 참조는 기본적으로 위법입니다.

    따라서, 다음과 같은 코드는 컴파일 되지 않습니다.

    int x;
    auto& & rx = x;
    C2529 : 'rx': you cannot create a reference to a reference

    하지만 이것이 허용되는 몇 가지 상황이 존재합니다. 이것을 Reference collapsing이라고 합니다.

     

    이번 글에서는 이 참조 축약에 대하여 살펴보도록 하겠습니다.

     


     

    1. 참조 축약 (Reference collpasing)

     

    더보기

    참조에 대한 참조는 위법이라는 것을 서론에서 살펴보았습니다.

    하지만 아래와 같은 코드는 적법합니다.

    class MyClass {};
    
    template<typename T>
    void func(T&& param) {
    }
    
    int main() {
        MyClass c;
        func(c);
        
        return 0;
    }

    위 코드의 c는 L-value이기 때문에, 인스턴스화 된 func는 다음과 같습니다.

    void func(MyClass& && param);

    서론과는 달리 위 코드는 정상적으로 컴파일 됩니다. 추가적으로, 위 코드는 아래와 같이 연역됩니다.

    void func(MyClass& param);

    위 템플릿 인스턴스가 L-value참조로 연역되는 자세한 내용은 다른 글 (Type deduction : Template)에 서술되어 있습니다.

    돌아와서, 위 코드가 적법한 이유는 일부 문맥에서 참조에 대한 참조가 허용되기 때문입니다.

    특정 문맥에서 허용되는 참조에 대한 참조는 다음과 같은 규칙에 의거하여 한 개의 참조로 축약됩니다.

    • 두 참조 모두 R-vlaue참조일 경우 R-value참조로 축약됩니다.
    • 그 외의 경우 (한 개라도 L-value일 경우) L-value참조로 축약됩니다.

     

    2. std::forward에 대하여

     

    더보기

    참조 축약은 이전 글 (std::move와 std::forward)에서 잠시 소개된 적이 있습니다.

    std::forward는 인수가 R-value일 경우에 R-value로 캐스팅을 수행하는 함수였습니다.

    template<typename T>
    void func(T&& fParam) {
        ...
        func2(std::forward<T>(fParam));
    }

    따라서 위와 같은 경우, func의 인수가 R-value일 경우(T가 비참조 형식일 경우) fParam을 R-value로 캐스팅합니다.

     

    이것을 구현하는 std::forward의 가능한 코드 중 하나는 아래와 같습니다.

    template<typename T>
    T&& forward(typename std::remove_reference<T>::type& param) {
        return static_cast<T&&>(param);
    }

    위 func와 forward, 그리고 사용자 정의 클래스인 MyClass를 통해 조금 더 자세히 살펴보도록 하겠습니다.

     

    func에 MyClass형식의 L-value가 전달되었을 경우를 살펴보겠습니다.

    이 경우 T는 MyClass&로 연역되며, std::forward는 std::forward<MyClass&>의 형태로 인스턴스화 합니다.

    이것을 std::forward의 구현에 대입하면 아래와 같습니다.

    MyClass& && forward(typename std::remove_reference<MyClass&>::type& param) {
        return static_cast<MyClass& &&>(param);
    }

     대입된 코드를 하나씩 풀어보도록 하겠습니다.

    • std::remove_reference<MyClass&>::type은 MyClass가 됩니다.
    • 반환 형식 및 캐스팅에 참조 축약 규칙을 적용하면, 두 가지 모두 MyClass&가 됩니다.

    따라서 std::forward의 인스턴스화 된 코드는 아래와 같습니다.

    MyClass& forward(MyClass& param) {
        return static_cast<MyClass&(param);
    }

    결과적으로, L-value가 전달되었을 경우 std::forward는 L-value참조를 받고 L-value참조를 반환합니다.

     

    func에 MyClass형식의 R-value가 전달되었을 경우를 살펴보겠습니다.

    이 경우 T는 MyClass입니다.std::forward는 std::forward<MyClass>의 형태로 인스턴스화 합니다.

    이것을 std::forward에 대입하면 아래와 같습니다.

    MyClass&& forward(typename std::remove_reference<MyClass>::type& param) {
        return static_cast<MyClass&&>(param);
    }

    대입된 코드를 하나씩 풀어보도록 하겠습니다.

    • std::remove_reference<MyClass>::type은 MyClass가 됩니다.
    • 반환 형식 및 캐스팅 등, 참조에 대한 참조가 있는 구문은 추가적으로 없습니다.

    따라서 std::forward의 인스턴스화 된 코드는 아래와 같습니다.

    MyClass&& forward(MyClass& param) {
        return static_cast<MyClass&&>(param);
    }

    결과적으로, func에 전달된 R-value인수는 R-value로 전달됩니다.

     

    위 std::forward의 인자에 사용된 typename std::remove_reference<T>::type은 C++11에서 아래와 같이 더 간결하게 구현 가능합니다.

    template<typename T>
    T&& forward(std::remove_reference_t<T>& param) {
        return static_cast<T&&>(param);
    }

     

    3. 참조 축약이 일어나는 문맥

     

    더보기

    참조 축약은 네 가지 문맥에서 일어납니다.

    • 템플릿 인스턴스
    • auto 변수
    • decltype
    • typedef와 using

    이 중, typedef의 사용에 대하여 예제를 살펴보도록 하겠습니다. (using도 유사합니다.)

    template<typename T>
    class MyClass {
    public:
        typedef T&& rValueRef;
        ...
    };

    위 클래스를 L-value참조 형식으로 인스턴스화 할 경우를 생각해보도록 하겠습니다.

    MyClass<int&> c;

    이 때, 템플릿 인스턴스화는 다음과 같이 이루어집니다.

    typedef int& && rValueRef;

    참조 축약에 의해, 이는 다음과 같이 축약됩니다.

    typedef int& rValueRef;

    변수의 이름인 rValueRef와는 맞지 않게, 변수가 L-value 참조로 연역되었습니다.

     

    이전 글 (보편 참조)에서 보편 참조를 R-value참조와 구분되는 것 처럼 설명했습니다.

    R-value참조와 다른 것은 맞지만, 아래의 조건이 만족될 경우 보편 참조는 R-value참조와 동일합니다.

    • 형식 연역에서 R-value와 L-value가 구분될 때
    • 참조 축약이 적용될 때

    템플릿, auto, decltype의 형식 연역과 관련한 내용은 다른 글에 정리되어 있습니다.

     


     

    참조 축약 문맥에 대한 정보, R-value및 L-value의 형식 연역에 대한 정보, 그리고 연역된 형식들을 대입 및 규칙을 적용하는 등, 내부적으로는 다소 복잡할 수 있는 과정들이 보편 참조라는 개념 하나에 녹아있습니다.

    보편 참조의 존재로 위 내용들을 숙지하며 프로그래밍 할 필요는 없지만, 이것이 왜 이렇게 되는지에 대한 참고 자료가 되었으면 합니다.

     

    감사합니다.

    댓글

Designed by Tistory.