ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Effective Modern C++] Type deduction : typeid, boost/type_index
    C++/Effective Modern C++ 2022. 1. 6. 12:42

    형식 연역 (Type deduction)은 소스코드에서 특정한 코드가 어떤 것으로 바뀌는 것 입니다.

    여기서 '특정한 코드'란 template, auto, decltype 등 이 있으며

    바뀌게 되는 '어떤 것' 에는 자료형부터, 함수, 람다식 등이 있습니다.

     

    이전 글에서 templateauto, 그리고 decltype을 통한 컴파일러에 의한 형식 연역을 살펴보았습니다.

    컴파일러가 어떤 규칙을 통해 형식 연역을 수행하는지 아는 것도 중요하지만

    그것을 확인하고 검증해야 하는 수단도 필요할 때가 있을 것 입니다.

     

    이번 글에서는 연역된 형식을 파악하는 방법을 알아보도록 하겠습니다.

     


     

    1. IDE 편집기

     

    더보기

    대부분의 IDE 편집기는 마우스 커서를 올리면 그 개체의 형식을 표시해 주는 기능이 있습니다.

    다음 사진은 Visual Studio 2019의 사진입니다.

     

    auto b가 std::vector<int>로 연역되었다.

    b에 마우스 커서를 올림으로, b가 어떤 형식으로 연역되었는지 볼 수 있습니다.

     

    이 기능은 IDE가 가지고 있는 컴파일러가 실행되기 때문입니다.

    따라서 컴파일이 가능할 정도로 (컴파일러가 코드를 파싱해서, 형식을 연역할 수 있을 정도로) 코드가 완성되어 있지 않다면, 위와 같은 기능은 대부분 작동하지 않을 수 있습니다.

     

    또한, 위와 같은 간단한 형식 외에 좀 더 복잡한 형식 연역이 적용될 경우, IDE에서 표시해주는 정보가 정확하지 않을 수 있습니다.

     

    2. 컴파일러 진단 메시지

     

    더보기

    형식을 파악하는 데 좋은 방법 중 하나는, 의도적인 오류를 발생시키는 것 입니다.

    C++의 오류 보고 메시지 중, 형식에 관련된 오류일 경우 대부분 관련된 형식을 출력해줍니다.

     

    예제를 살펴보도록 하겠습니다.

    #include <iostream>
    
    template<typename T>
    class TD;
    
    int main() {
        int x(10);
        auto a(x);
        TD<decltype(a)> a_type;
    }
    C2079 : 'a_type'은(는) 정의되지 않은 class 'TD<int>'을(를) 사용합니다.

    위 클래스는 선언만 되고, 정의되지 않아 인스턴스화 할 수 없는 클래스 입니다.

    main함수에서 인스턴스화 할 수 없는 클래스를 인스턴스화 하려고 하기 때문에, 컴파일 에러가 발생합니다.

    에러 메시지에 출력된 TD<int>를 통해, a_type이 연역하려던 형식이 int임을 알 수 있습니다.

     

    위와 같이, 에러를 발생시킴으로 형식을 파악하는 방법을 사용할 수 있습니다.

     

    3. 실행 시점 출력 : typeid

     

    더보기

    typeid 함수는 std::type_info라는 객체를 반환합니다.

    이 객체의 멤버 함수인 name()을 통해서, 실행 시점에서 객체의 형식을 확인할 수 있습니다.

     

    예제를 살펴보도록 하겠습니다.

    #include <iostream>
    
    int main() {
        const int a(10);
        auto b(a);
        auto c("asdf");
        std::cout << typeid(b).name() << "\n";
        std::cout << typeid(c).name() << "\n";
    }
    int
    char const *

    위의 출력은 Microsoft 컴파일러로 컴파일 한 출력입니다.

    이것을 GNU 컴파일러로 컴파일하면 아래와 같이 출력됩니다.

    i
    PKc

    i는 int, c는 char, 그리고 PK는 Pointer to Konst (Const)를 의미합니다.

     

    위 예제를 통해 typeid가 컴파일러에 따라 난해함의 차이는 있지만, 어느정도 도움이 되는 정보를 준다는 생각을 할 수 있습니다.

    그러면, 조금 더 복잡한 예제를 보도록 하겠습니다.

    #include <iostream>
    
    template<typename T>
    void function(const T& param) {
        std::cout << "T : " << typeid(T).name() << "\n";
        std::cout << "param : " << typeid(param).name() << "\n";
    }
    
    int main() {
        const int a(10);
        auto b(a);
        auto c("asdf");
        function(b);
        function(c);
    }
    T : int
    param : int
    T : char const *
    param : char const *
    T : i
    param : i
    T : PKc
    param : PKc

    두 컴파일러가 동일한 정보를 출력하는것으로 보아, 컴파일러의 문제는 아니라고 볼 수 있을 것입니다.

    하지만, 출력이 옳은 것인가를 생각해보면 그렇지도 않습니다.

    이전 글 (템플릿의 형식 연역)에서 살펴본 내용에 의하면, 위 코드의 T와 param은 같을 수 없습니다.

     

    이렇게 틀리게 보고되는 이유는 C++ 표준을 따라야 하기 때문입니다.

    표준에 의하면, std::type_info::name은 반드시 주어진 형식을 템플릿 함수에 값 전달 매개변수로써 전달된 것 처럼 취급해야 합니다.

    값 전달 방식으로 생각해볼 경우 참조성이 무시되고, 이후 상수성도 무시되기 때문에 위와 같은 결과가 보고되는 것 입니다.

     

    다음 문단에서 이어집니다.

     

    4. 실행 시점 출력 : boost/type_index

     

    더보기

    이전 문단의 내용에 의하면, typeid를 통한 실행 시점의 형식 출력은 정확하지 않습니다.

    형식 연역의 규칙을 정확하게 이해하고 사용하는 것이 가장 바람직하겠지만, 혹시 필요한 상황이 생긴다면 지금부터 소개할 라이브러리가 도움이 될 것 같습니다.

     

    Boost는 C++라이브러리 입니다.

    자세한 내용은 다른 글 (Boost 라이브러리 설치)에서 다루었습니다.

    Boost의 type_index를 이용하면, typeid보다 정확한 정보를 얻을 수 있습니다.

    예제를 살펴보도록 하겠습니다.

    #include <iostream>
    #include <boost/type_index.hpp>
    
    template<typename T>
    void function(const T& param) {
        std::cout << "T(boost) : " << boost::typeindex::type_id_with_cvr<T>().pretty_name() << "\n";
        std::cout << "param(boost) : " << boost::typeindex::type_id_with_cvr<decltype(param)>().pretty_name() << "\n";
    }
    
    int main() {
        const int a(10);
        auto b(a);
        auto c("asdf");
        function(b);
        function(c);
    }
    T(boost) : int
    param(boost) : int const &
    T(boost) : char const *
    param(boost) : char const * const &

    boost::typeindex::type_id_with_cvr 템플릿은 자신에게 전달된 형식 인수의 참조 한정사를 그대로 보존합니다.

    type_id_with_cvr의 cvr이 Const, Volatile, Reference(&)를 의미하는 것 입니다.

    이 함수 템플릿은 boost:typeindex::type_index 객체를 반환하고, 그 객체의 pretty_name 함수가 객체의 형식을 담은 std::string 객체를 반환합니다.

     


     

    형식 연역을 직접 확인하는 방법을 몇 가지 알아보았습니다.

    연역된 형식을 확인하는 방법을 아는 것은 디버깅에 도움이 될 것입니다.

    하지만 그렇다고 해서 형식 연역 규칙을 숙지할 필요가 없다고는 할 수 없습니다.

    최종적으로는 규칙을 숙지하여, 형식 연역과 관련된 오류가 발생하지 않도록 하는 것이 최선일 것 입니다.

     

    읽어주셔서 감사합니다.

    댓글

Designed by Tistory.