- typedef는 템플릿화를 지원하지 않지만, 별칭 선언은 지원한다.
STL 컨테이너들을 사용하다 보면 다음과 같은 타입을 흔히 만난다.
std::unique_ptr<std::unordered_map<std::string, std::string> >
이런 타입을 여러 번 입력하고 싶지는 않을 것이다. 그냥 생각만 해도 손목 터널 증후군에 걸릴 것 같다.
이럴 때 사용할 수 있는 것이 typedef 이다.
typedef std::unique_ptr<std::unordered_map<std::string, std::string>> UPtrMapSS;
그런데 typedef는 너무 C++98스러운 유물이다. C++11에서도 typedef가 작동하지만, C++11은 별칭 선언(alias declaration) 이라는 것도 제공한다.
using UPtrMapSS = std::unique_ptr<std::unordered_map<std::string, std::string> >;
typedef와 하는 일은 정확히 동일하다. 하지만 typedef보다 우월한 점이 몇 가지 있다.
소소한 장점으로 함수 포인터가 관여하는 타입을 다룰 때 별칭 선언 쪽을 더 쉽게 이해하는 사람들이 많다는 점을 들 수 있다.
FP는 int 하나와 const std::string& 하나를 받고 아무것도 돌려주지 않는 함수와 동의어이다.
typedef void (*FP)(int, const std::string&);
위와 같은 의미
using FP = void (*)(int, const std::string&);
하지만 이것만으로는 typedef 대신 별칭 선언을 선택해야 할 강력한 이유가 되지 못한다.
강력한 이유는 따로 있다. 바로 템플릿이다.
typedef는 템플릿화를 할 수 없지만 별칭 선언은 템플릿화할 수 있다.
이런 별칭 선언을 별칭 템플릿(alias templates)이라고 부른다.
typedef로 템플릿화를 흉내내려면, 불편하게 struct 안에 내포된 typedef들을 활용하는 편법을 써야 한다.
template <typename T> // MyAllocList<T>::type은 std::list<T,MyAlloc<T>> 와 동의어이다.
struct MyAllocList {
typedef std::list<T, MyAlloc<T> > type;
};
MyAllocList<Widget>::type lw;
반면 별칭 템플릿은 훨씬 더 간단하고, 직접적으로 표현할 수 있다.
template <typename T> // MyAllocList<T>는 std::list<T, MyAlloc<T>>
using MyAllocList = std::list<T, MyAlloc<T> >;
MyAllocList<Widget> lw;
- 별칭 템플릿에서 "::type" 접미어를 붙일 필요가 없다. 템플릿 안에서 typedef를 지정할 때에는 "typename" 접두사를 붙어야 하는 경우가 많다.
typedef를 사용하면 상황이 더 나빠질 수도 있다.
template <typename T>
class Widget {
private:
typename MyAllocList<T>::type list;
};
방금 예에서 MyAllocList<T>::type은 템플릿 타입 매개변수(T)에 의존적인 타입을 지칭한다.
즉, MyAllocList<T>::type은 소위 의존적 타입(dependent type)이며, C++에서는 의존적 타입의 이름 앞에 반드시 typename을 붙여야 한다 (Effective C++ 의 항목 42 참고).
반면 MyAllocList를 별칭 템플릿으로 정의하면 typename을 붙일 필요가 없다. (또한 번거로운 "::type" 접미어도
붙일 필요가 없다).
template <typename T>
using MyAllocList = std::list<T, MyAlloc<T> >;
template <typename T>
class Widget {
private:
MyAllocList<T> list; // "typename" 없음
... // "::type" 없음
};
- C++14는 C++11의 모든 타입 특성 변환에 대한 별칭 템플릿들을 제공한다.
템플릿 메타 프로그래밍(TMP)을 하다 보면 어떤 타입 T가 주어졌을 때, T에 담긴 임의의 const 한정사나 기타 참조 한정사들을 제거하거나 붙여야 할 경우가 있다.C++은 이런 종류의 변환을 타입 특성(type trait)의 형태로 수행할 수 있는 도구들을 제공한다.
여기서 타입 특성은 <type_traits> 헤더 안에 있는 일단의 템플릿들을 말한다.그 헤더에는 수십 가지 타입 특성들이 있는데, 그것들이 모두 타입 변환을 수행하지는 않지만, 하나의 예측 가능한 인터페이스를 제공하는 것은 사실이다.
적용하고자 하는 타입 T가 주어졌을 때, 변환을 적용한 결과로 나온 타입은 항상 std::변환<T>::type이다.
이런 변환들을 적용할 때 "::type"라는 접미어를 붙여야 한다는 점에 주목하자.템플릿 안의 타입 매개변수에 그 접미어를 붙인다면, 그 앞에 typename도 반드시 붙여야 한다.
이것은, C++11의 타입 특성들이 모두 템플릿화된 struct 안에 내포된 typedef들로 구현되었다는 사실 때문에 필요한 것이다.
다행히 표준 위원회는 C++11의 모든 타입 변환에 대한 별칭 템플릿 버전들을 C++14에 포함시켰다.
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++14에는 이러한 모든 타입 변환에 대한 별칭 템플릿 버전들이 포함되었다.
std::remove_const<T>::type // C++11: const T -> T
std::remove_const_t<T> // 해당 C++14 버전
std::remove_reference<T>::type // C++11: T&/T&& -> T
std::remove_reference_t<T> // 해당 C++14 버전
std::add_lvalue_reference<T>::type // C++11: T -> T&
std::add_lvalue_reference_t<T> // 해당 C++14 버전
C++14를 사용할 수 없는 상황이라고 하더라도, 해당 별칭 템플릿들을 스스로 작성하는 것이 아이들 장난 수준이다.
C++11의 언어 기능들만 있으면 아이들이라도 얼마든지 패턴을 흉내낼 수 있다.
다음을 출발점으로 삼기 바란다.
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;
'PROGRAMMING > Effective Modern C++' 카테고리의 다른 글
11. 정의되지 않은 비공개 함수보다 삭제된 함수를 선호하라. (0) | 2020.12.25 |
---|---|
10.범위 없는 enum보다 범위 있는 enum을 선호하라. (0) | 2020.12.25 |
8. 0과 NUll보다 nullptr을 선호하라. (0) | 2020.12.20 |
7. 객체 생성시 괄호()와 중괄호{}를 구분하라. (0) | 2020.12.20 |
6. auto가 원치 않은 타입으로 추론될 때에는 명시적 탕비의 초기치를 사용하라. (0) | 2020.12.19 |
댓글