티스토리 뷰

형식 연역 결과를 직접 확인하는 수단은 소프트웨어 개발 과정에서 정보가 필요한 시점에 따라서 다릅니다. 이번 항목에서는 다음과 같은 3가지 시점에서 형식 연역 정보를 얻는 방법을 살펴보겠습니다 :

  • 코드를 작성 및 수정하는 시점
  • 컴파일 시점
  • 실행시점

 

 

 

코드를 작성 및 수정하는 시점


IDE 편집기

IDE의 코드 편집기 중에는 프로그램 개체(변수, 매개변수, 함수 등) 위에 마우스 커서를 올리면 그 개체의 형식을 표시해 주는 것이 많습니다. 예를 들어, 다음과 같이 코드가 있을 때,

const int the Answer= 42;
auto x = theAnswer;
auto y = &theAnswer;

 

IDE 편집기는 x의 연역된 형식이 int이고 y의 연역된 형식이 const int*임을 표시해 줍니다. IDE가 이런 종류의 정보를 제공할 수 있는 것은 IDE 내 컴파일러(또는, 적어도 이러한 컴파일러의 앞단(front))가 실행되기 때문입니다. 컴파일러가 코드를 파싱해서 형식 연역을 수행할 수 있을 정도로 편집기의 코드가 완성되어 있지 않다면 편집기는 요청된 개체의 형식을 표기할 수 없습니다.

편집기에서 제공하는 정보는 간단한 형식의 경우에는 쓸만하지만, 좀 더 복잡한 형식이 관여할 때는 IDE가 표시한 정보가 도움되지 않을 수 있습니다.

 

 

 

컴파일 시점


컴파일러의 진단 메시지

일반적으로 컴파일러가 연역한 형식을 파악하는 데 효과적인 방법 중 하나는 원하는 형식 때문에 컴파일에 문제가 발생하게 만드는 것입니다. 거의 항상, 문제를 보고하는 오류 메시지에는 문제를 일으킨 형식이 언급되어 있습니다. 이것을 가능하게 하는 방법을 앞의 예제 코드 x와 y에 적용하면, 클래스 템플릿 하나를 정의 없이 선언만 해둡니다.

template<typename T>
class TD;

 

이 클래스를 인스턴스화하려면, 인스턴스화할 템플릿 정의가 없어서 컴파일 오류가 발생하게 됩니다. 결론적으로 x와 y의 형식을 알고 싶다면 해당 형식들로 TD를 인스턴스화해보면 됩니다.

TD<decltype(x)> xType;
TD<decltype(y)> yType;

 

다음은 위 코드에 대해 컴파일러 중 하나가 뱉어낸 오류 메시지의 일부분입니다 :

error : aggregate 'TD xType' has incomplete type and cannot be defined
error : aggregate 'TD<const int *> yType' has incomplete type and connot be defined

 

다른 한 컴파일러도 형태는 다르지만 본질적으로 동일한 정보를 제공합니다(Visual Studio 2013의 컴파일러) :

error : 'xType'은(는) 정의되지 않은 class 'TD' 을(를) 사용합니다.
error : 'yType'은(는) 정의되지 않은 class 'TD<const int *>' 을(를) 사용합니다.

 

 

 

실행시점


실행시점 출력

형식 정보 printf를 이용하여 출력의 서식을 제어하는 방법이 있습니다. 이 방식의 까다로운 점 하나는, 원하는 형식으로부터 표시에 적합한 형태의 텍스트를 만들어내는 것 입니다. typeid와 std::type_info::name을 사용하면 될 것 같기는 합니다.

다음은 typeid를 이용해서 출력하는 예입니다 :

std::cout << typeid(x).name << '\n';
std::cout << typeid(y).name << "\n";

 

이 접근 방식은 x나 y같은 객체에 대해 typeid를 적용하면 std::type_info 형식의 객체가 산출되며, 그 std::type_info 객체에는 name이라는 멤버 함수가 있으며, 그 멤버 함수는 형식의 이름을 나타내는 C스타일 문자열(즉, const char*)을 돌려준다는 가정이 있습니다.

표준에 따르면, std::type_info__name이 의미 있는 무언가를 돌려준다는 보장은 없지만, 대체로 구현(컴파일러)들은 개발자에게 도움이 되고자 노력합니다. GNU 및 Clang 컴파일러는 x의 형식이 "i"라고 보고하고, y의 형식이 "PKi"라고 보고합니다. i가 int 이고, PK가 pointer to konst const 임을 알고 나면 납득할 수 있습니다(두 컴파일러 모두, 이런 난도질된(mangled) 형식들을 해독해 주는 c++flit 도구를 지원합니다). Microsoft 컴파일러의 출력은 이보다 덜 난해해서, x에 대해서는 int를 y에 대해서는 int const * 보고합니다.

컴파일러 오류 메시지를 이용해서 형식들에 대해 정확한 결과를 얻었다고 형식 보고 문제가 완전히 해결됬다는 것은 너무 성급한 일입니다. 좀 더 복잡한 예는 다음과 같습니다 :

template<typename T>
void f(const T& param);                        // 호출할 템플릿 함수

std::vector<Widget> createVec();        // 팩토리 함수

const auto vw = createVec();

if(!vw.empty()) {
    f(&vw[0]);
    ...
}

 

여기서 f의 템플릿 형식 매개변수 T와 매개변수 param에 대해 어떤 형식들이 추론되었는지를 직접 확인할 수 있다면 유용할 것입니다. 이를 위해 typeid를 사용하는 것의 문제점은 명백합니다. 다음과 같이 알고 싶은 형식을 표시하기 위해 다음과 같은 코드를 추가해보겠습니다 :

using std::cout;
cout << "T =     " << typeid(T).name() << "\n";
cout << "param = " << typeid(param).name() << "\n";

 

GNU 및 Clang 컴파일러로 만든 실행 파일들의 출력은 다음과 같습니다 :

T =        PK6Widget
param = PK6Widget

 

여기서 6은 클래스 이름의 글자 수 입니다. 결과적으로 이 컴파일러들은 T와 param 형식이 둘다 const Widget*임을 말해줍니다. Microsoft 역시 동일한 정보를 알려줍니다.

T =     class Widget const *
param = class Widget const*

 

세 컴파일러가 같은 정보를 출력했다는 점을 생각하면, 그 정보는 정확할 것이다. 그러나 좀 더 자세히 살펴보면, 템플릿 f에서 param의 선언된 형식은 const T&인데도 T와 param의 형식이 같습니다. 즉, T가 int 였다면, param의 형식은 그와는 다른 const int&가 되어야 합니다.

 

안타깝게도 std::type_info::name의 정보는 믿을 만하지 않습니다. 더 나아가서, 표준을 준ㅅ하려면 그렇게 틀리게 보고할 필요가 있습니다. 표준에 따르면, std::type_info::name은 반드시 주어진 형식을 마치 템플릿 함수에 값 전달 매개변수로서 전달된 것처럼 취급해야 합니다. 항목1에서 설명하듯이, 값 전달의 경우 형식이 참조이면 참조성이 무시되며, 참조 제거후 const 역시 무시됩니다. 그래서 const Widget* const&인 param의 형식이 const Widget*으로 보고된 것입니다.

 

마찬가지로 안타까운 일은, IDE 편집기가 표시하는 형식 정보 역시 믿을 만하지 않다는, 또는 적어도 믿을 만하게 유용하지는 않다는 점입니다. 같은 예에 대해 어떤 IDE 편집기는 T의 형식을 다음과 같이 보고합니다 :

T의 형식
const std::_Simple_dtypes<std::_Wrap_alloc<std::_Vec_base_types<Widget, std::allocater>::_Alloc>::value_type>::value_type *

param의 형식
const std::_Simple_types<...>::value_type* const&

 

T의 형식보다는 덜 흉악하지만, 중간의 "..."가 혼동될 수 있습니다. 즉, 일부에 대한 생략을 "..."로 표현했다는 것입니다. 운이 좋다면, 독자 여러분들이 사용하는 IDE는 이보다 나은 결과를 보여줄 수 있습니다.

 

라이브러리를 더 믿는 독자는 std::type_info::name과 IDE가 실패하는 경우에서도 Boost TypeIndex 라이브러리(흔히 Boost.TypeIndex라고 표기합니다)는 성공하도록 설계되어 있다는 점을 반길 것입니다. 이 라이브러리는 표준 C++ 의 일부가 아니지만, 어차피 IDE나 TD같은 템플릿도 표준 C++에 속하는 것은 아닙니다.

 

다음은 앞의 함수 f에 대한 정확한 형식 정보를 Boost.TypeIndex를 이용해서 얻는 방법을 보여주는 예제 코드입니다 :

#include <boost/type_index.hpp>

template<typename T>
void f(const T& param)
{
    using std::cout;
    using boost::typeindex::type_id_with_cvr;

    // T를 표시
    cout << "T =     "
            << type_id_with_cvr<T>().pretty_name()
            << "\n";

    // param을 표시
    cout << "param = "
            << type_id_with_cvr<decltype(param).pretty_name()
            << "\n";

 

여기서 boost::typeindex::type_id_with_cvr이 자신에게 전달된 형식 인수의 const나 volatile, 참조 한정사들을 그대로 보존하기 때문입니다(with_cvr을 보면 추측할 수 있을 것입니다).

 

 

 

기억해 둘 사항들


  • 컴파일러가 연역하는 형식을 IDE 편집기나 컴파일러 오류 메시지, Boost TypeIndex 라이브러리를 이용해서 파악할 수 있는 경우가 많다.
  • 일부 도구의 결과는 유용하지도 않고 정확하지도 않을 수 있으므로, C++의 형식 연역 규칙들을 제대로 이해하는 것은 여전히 필요한 일이다.

 

 

 

레퍼런스


[Effective Modern C++ - C++11과 C++14를 효과적으로 사용하는 42가지 방법] 항목 4 : 연역된 형식을 파악하는 방법을 알아두라

댓글