Item 9 - alias declaration
마지막 수정 시각: 2020-10-26 22:03:12
std::unique_ptr<std::unordered_map<std::string, std::string>>
같은 타입을 생각해보자. STL 컨테이너와 스마트 포인터가 섞이게 되면 타입 이름이 굉장히 길어지게 되고, 이건 코드 가독성을 심하게 훼손한다. C++98까지는 이런 상황에 typedef를 썼다.
typedef
std::unique_ptr<std::unordered_map<std::string, std::string>>
UPtrMapSS;
C++11에서는 이런 경우에 typedef대신 alias declaration을 사용한다.
using UPtrMapSS =
std::unique_ptr<std::unordered_map<std::string, std::string>>
typedef나 alias declaration이나 하는 역할은 완전히 똑같고, 기술적으로도 무슨 차이가 생기거나 하는 건 아니다. 하지만 typedef에 비해 alias declaration이 훨씬 가독성이 좋고, 헷갈리는 일이 덜하다. 특히 함수 포인터같은 경우 typedef는 문법이 눈에 잘 안들어오고, 기억하기도 힘들다.
typedef void (*FP)(int, const std::string&);
using FP = void (*)(int, const std::string&);
어떤 코드가 가독성이 더 높은 지는 자명할 것이다. 여기에 더불어, alias declaration은 typedef가 따라 올 수 없는 굉장한 장점이 있다. 바로 템플릿을 이용할 수 있다는 것이다.
template<typename T>
using MyAllocList = std::list<T, MyAlloc<T>>;
MyAllocList<Widget> lw;
위와 같이 alias declaration과 템플릿을 같이 이용할 수 있다. 물론 적절하게 응용하면 typedef으로도 이와 유사한 구현을 할 수 있다(실제로도 자주 이용되는 방법이다).
template<typename T>
struct MyAllocList
{
typedef std::list<T, MyAlloc<T>> type;
};
MyAllocList<Widget>::type list;
여기서 typedef를 이용한 위 코드의 경우 템플릿과 함께 쓰면 귀찮은 일이 많이 생긴다.
template<typename T>
class Widget
{
private:
typename MyAllocList<T>::type list;
};
여기서 MyAllocList<T>::type
은 T에 의존적이다. 여기서 typename 키워드를 통해 MyAllocList<T>::type
이 타입이라는 걸 알려주지 않으면 에러가 발생한다. 왜냐하면, 우리는 MyAllocList<T>::type
이 타입이라는 걸 알지만 컴파일러는 이게 타입을 가리키는 지 알 수 없기 때문이다. 누군가가 MyAllocList의 특정한 T 타입에 대해서만 템플릿 구체화를 통해 type을 멤버 변수로 선언해버릴지도 모를 일 아닌가? 그렇게 되면 MyAllocList<T>::type
은 더 이상 타입이 아니게 된다. 즉, 컴파일러 스스로는 템플릿 내부에서 이런 의존적인 타입에 대해 그게 실제로 타입인지 아닌지 판단할 수 없으므로 typename 키워드를 통해 그게 타입임을 컴파일러에게 알려주어야하는 것이다.
반면에 alias declaration을 이용하면 typename도, ::type도 필요없다.
template<typename T>
using MyAllocList = std::list<T, MyAlloc<T>>;
template<typename T>
class Widget
{
private:
MyAllocList<T> list; //simple!
};
이 경우 MyAllocList는 alias declaration을 이용해 정의된 것이기 때문에 컴파일러는 이게 타입임을 다른 지시자 없이도 확실히 알 수 있다. 따라서 typename을 붙여주지 않아도 정상적으로 동작한다.
type traits
템플릿 메타 프로그래밍(TMP)에서, 어떤 T 타입에 레퍼런스를 붙이 거나 그걸 const T로 만들거나 하는 등등 타입을 건드려야하는 경우가 왕왕 있다. C++ 11에서는 이런 경우의 편의성을 위해 <type_traits> 헤더 파일에 이와 관련된 타입 변환 작업을 하는 녀석들을 만들어 놓았다.
std::remove_const<T>::type //const T -> T
std::remove_reference<T>::type // T&,T&& -> T
std::add_lvalue_reference<T>::type // T -> T&
척보면 알겠지만 C++11의 type traits는 내부적으로 struct와 typedef를 중첩해서 구현해놓았다. 그래서 템플릿에서 이 놈들을 쓰기 위해선 앞에 typename 키워드를 꼭 붙여줘야한다. 근데 이건 누가봐도 귀찮다. 그래서 C++14에서는 이 부분을 수정해서, alias declaration을 이용해서 구현해놓은 버전을 제공한다.
std::remove_const<T>::type //C++11
std::remove_const_t<T> //C++14
std::remove_reference<T>::type //C++11
std::remove_reference_t<T> //C++14
std::add_lvalue_reference<T>::type // C++11
std::add_lvalue_reference_t<T> // C++14
간단히 말하자면 std::transformation<T>::type 꼴이 std::transformation_t<T> 꼴로 바뀌었다고 보면 된다.
만약 C++11에서도 C++14 스타일로 위의 type traits를 쓰고 싶다면 아래와 같은 코드를 쓰면 된다. 사실 굉장히 간단하다.
template <class T>
using remove_const_t = typename remove_const<T>::type;
template <class T>
using remove_reference_t = typename remove_reference<T>::type;
template <class T>
using add_lvalue_reference_t =
typename add_lvalue_reference<T>::type;