[Effective Modern C++] 4. 추론된 타입을 파악하는 방법을 알아두자
IDE 편집기
IDE(통합 개발 환경)의 코드 편집기 중에는 프로그램 객체(변수, 매개변수, 함수 등) 위에 마우스 커서를 올리면 그 객체의 타입을 표시해 주는 것이 많다.
const int theAnswer = 42;
auto x = theAnswer; // int
auto y = &theAnswer; // const int*
이런 일이 가능하려면 편집기의 코드가 어느 정도는 컴파일 가능한 상태여야 한다. 컴파일러가 코드를 파싱 해서 타입 추론을 수행할 수 있을 정도로 편집기의 코드가 완성되어 있지 않으면 편집기는 요청된 객체의 타입을 표시할 수 없다.
일반적으로 int와 같은 간단한 타입의 경우에는 IDE가 알려준 정보가 쓸만하지만, 좀 더 복잡한 타입이 관여할 때에는 IDE가 표시한 정보가 그리 도움이 되지 않을 수도 있다.
컴파일러의 진단 메시지
일반적으로 컴파일러가 추론한 타입을 파악하는 데 효과적인 방법 중 하나는, 원하는 타입 때문에 컴파일에 문제가 발생하게 만드는 것이다. 거의 항상 문제를 보고하는 오류 메시지에는 문제를 일으킨 타입이 언급되어 있다.
우선, 클래스 템플릿 하나를 정의 없이 선언만 해둔다. 이 템플릿을 인스턴스화하려고 하면 인스턴스화할 템플릿 정의가 없어서 컴파일 오류가 발생하고, 해당 오류 메시지에 우리가 원하는 정보가 들어있다.
아래의 예제를 봐보자.
template <typename T> // TD를 선언만 해 둔다; TD는 "Type Displayer(타입 표시기)"를 뜻한다.
class TD;
---
// 다음과 같은 변수 x, y가 있을 때,
const int theAnswer = 42;
auto x = theAnswer;
auto y = &theAnswer;
// x와 y의 타입을 알고 싶다면 해당 타입들로 TD를 인스턴스화해보면 된다.
// x와 y의 타입들이 담긴 오류 메시지들이 나온다.
TD<decltype(x)> xType;
TD<decltype(y)> yType;
// 이 예제 코드에는 "변수이름Type" 형태의 변수 이름들이 쓰였는데, 이렇게 하면 오류 메시지에서 원하는 변수의 타입을 좀 더 쉽게 찾아낼 수 있다.
// 다음은 위의 코드에 대해 어떤 컴파일러가 토해 낸 오류 메시지의 일부분이다.
error: aggregate 'TD<int> xType' has incomplete type and cannot be defined
error: aggregate 'TD<const int *> yType' has incomplete type and cannot be defined
// 다른 한 컴파일러도 형태는 다르지만 본질적으로 동일한 정보를 제공한다.
error: 'xType'은(는) 정의되지 않은 class 'TD<int>'을(를) 사용합니다.
error: 'yType'은(는) 정의되지 않은 class 'TD<const int *>'을(를) 사용합니다.
// 서식의 차이는 있지만, 이 기법으로 시험해 본 거의 모든 컴파일러는 유용한 타입 정보를 제공했다.
결론적으로, x의 타입을 알고 싶다면 해당 타입들로 TD(정의 없는 클래스 템플릿)를 인스턴스화 해보면 된다.
Runtime Output
// x와 y의 타입에 대해 보여준다.
std::cout << typeid(x).name() << '\n';
std::cout << typeid(y).name() << '\n';
위의 예제에서는 타입이 정확하기 때문에 typeid와 std::type_info::name을 사용하면 그만이라고 생각할 수 있지만, 아쉽게도 std::type_info::name의 정보는 믿을 만하지 않다.
좀 더 복잡한 예제를 봐보자.
template <typename T>
void f(const T& param);
std::vector<Widget> createVec(void);
const auto vw = createVec();
if (!vw.empty())
{
f(&vw[0]);
...
}
template<typename T>
void f(const T& param)
{
using std::cout;
cout << "T = " << typeid(T).name() << '\n'; // T의 타입
cout << "param = " << typeid(param).name() << '\n'; // param의 타입
}
// GNU and Clang compilers
T = PK6Widget
param = PK6Widget
// Microsoft’s compiler
T = class Widget const *
param = class Widget const *
// 컴파일러에 따라 결과가 다르다.
---
// 어떤 IDE 편집기는 T의 타입을 다음과 같이 보고한다.
const std::_Simple_types<std::_Wrap_alloc<std::_Vec_base_types<Widget, std::allocator<Widget> >::_Alloc>::value_type>::value_type *
// 그리고 param의 타입은 다음과 같이 보고한다.
const std::_Simple_types<...>::value_type *const &
// T에 대한 타입보다는 덜 흉악하지만, 중간의 "..."가 좀 혼동될 것이다.
// 해당 IDE는 "T의 타입의 일부에 해당하는 모든 것을 생략했음"이라는 뜻을 "..."로 표현했다.
// 행운이 따른다면, 여러분이 사용하는 IDE는 이보다는 나은 결과를 보여줄 것이다.
표준에 따르면, std::type_info::name은 반드시 주어진 타입을 마치 템플릿 함수에 값 전달 매개변수로서 전달된 것처럼 취급해야 한다.
따라서 항목 1에서 설명하듯이, 값 전달의 경우 만일 타입이 참조이면 참조성이 무시되며, 참조를 제거한 후의 타입이 const 또는 volatile이면 해당 const성 또는 volatile성 역시 무시된다.
그래서 실제로는 const Widget* const&인 param의 타입이 그냥 const Widget*로 보고된다.
마찬가지로 IDE 편집기가 표시하는 타입 정보 역시 믿을만하지 않거나, 최소한 신뢰할 수 있을 만큼 유용하지 않다.
std::type_info::name과 IDE가 실패하는 경우에서도 Boost TypeIndex라이브러리(흔히 Boost. TypeIndex)는 성공하도록 설계되어 있다는 점을 반길 것이다.
다음은 정확한 타입 정보를 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<param>().pretty_name() << '\n';
}
// 이 코드가 우리가 원했던 정보를 출력하는 이유는, 함수 템플릿 boost::typeindex::type_id_with_cvr이 자신에게 전달된 타입 인수(우리가 알고자 하는 타입)의 const나 volatile, 참조 한정사들을 그대로 보존하기 때문이다(이름에 "with_cvr"이 붙은 이유가 바로 그것이다).
// type_id_with_cvr은 하나의 boost::typeindex::type_index 객체를 산출하며, 그 객체의 pretty_name 멤버 함수는 타입을 사람이 보기에 좋게 표현한 문자열을 담은 std::string 객체를 돌려준다.
// 이제 이전에 보았던 예제를 다시 보자.
std::vector<Widget> createVec(void); // 팩토리 함수
const auto vw = createVec(); // vw를 팩토리 함수의 반환값으로 초기화한다.
if (!vw.empty())
{
f(&vw[0]); // f를 호출
...
}
// GNU 컴파일러와 Clang 컴파일러에서, Boost.TypeIndex를 사용하는 예제는 다음과 같은 (정확한)출력을 낸다.
T = Widget const*
param = Widget const* const&
// Microsoft 컴파일러의 결과도 본질적으로 동일하다.
T = class Widget const *
param = class Widget const * const &
요약
- 컴파일러가 추론하는 타입을 IDE편집기나 컴파일러 오류 메시지, Boost TypeIndex 라이브러리를 이용해서 파악할 수 있는 경우가 많다.
- 일부 도구의 결과는 유용하지도 않고 정확하지도 않을 수 있으므로, C++의 타입 추론 규칙들을 제대로 이해하는 것이 필요하다.
'Books > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 6. auto가 원치 않은 타입으로 추론 될 때에는 명시적 타입의 초기화를 생각하자 (0) | 2022.08.27 |
---|---|
[Effective Modern C++] 5. 명시적 타입 선언보다는 auto를 선호하자 (0) | 2022.08.15 |
[Effective Modern C++] 3. decltype의 작동 방식을 숙지하자 (0) | 2022.08.06 |
[Effective Modern C++] 2. auto의 타입 추론 규칙을 숙지하자 (0) | 2022.08.06 |
[Effective Modern C++] 1. 템플릿 타입 추론 규칙을 숙지하자 (0) | 2022.08.06 |
댓글
이 글 공유하기
다른 글
-
[Effective Modern C++] 6. auto가 원치 않은 타입으로 추론 될 때에는 명시적 타입의 초기화를 생각하자
[Effective Modern C++] 6. auto가 원치 않은 타입으로 추론 될 때에는 명시적 타입의 초기화를 생각하자
2022.08.27 -
[Effective Modern C++] 5. 명시적 타입 선언보다는 auto를 선호하자
[Effective Modern C++] 5. 명시적 타입 선언보다는 auto를 선호하자
2022.08.15 -
[Effective Modern C++] 3. decltype의 작동 방식을 숙지하자
[Effective Modern C++] 3. decltype의 작동 방식을 숙지하자
2022.08.06 -
[Effective Modern C++] 2. auto의 타입 추론 규칙을 숙지하자
[Effective Modern C++] 2. auto의 타입 추론 규칙을 숙지하자
2022.08.06