원하는 만큼 포인터에 별붙이기 int*****
포인터 타입에 별을 원하는만큼 붙여보고 싶으신적 없으세요?
가슴이 따뜻해지는 기념일에 가족, 친구, 애인분께 별이 가득히 붙은 포인터 변수 하나 선물해드리는게 어떨까요?
먼저 알아야 할 문법들
template<>
using Type = type;
decltype
템플릿 메타프로그래밍 (template metaprogramming)
C++의 제너릭 프로그래밍에 해당하는 template
은 다른언어의 제너릭 프로그래밍과 다른 점이 많다.
template<typename T>
struct wrapper {
T item;
};
wrapper<char> wrap;
우리가 이런 코드를 작성했다고 했을 때, C++은 런타임에 wrapper<char>
을 처리하지 않는다.
컴파일러는 그저 다음과 같은 코드를 만들어서 프로그램에 집어넣을 뿐이다.
struct wrapper<char> {
char item;
};
만약 코드에서 추가로 wrapper<int>, wrapper<long long>
이 나타난다면 컴파일러는 추가로 새로운 wrapper를 하드코딩해서 프로그램에 집어넣는다.
struct wrapper<int> {
int item;
};
struct wrapper<long long> {
long long item;
};
이런식으로 컴파일러에게 필요할때마다 하드코딩된 코드를 생성하도록 하는 것을 템플릿 메타 프로그래밍이라고 한다.
간단하게 타입을 바꿔치기 하는 것 뿐만 아니라, 조금 복잡한 작업들도 할 수 있는데, 다음 코드를 봐보자.
template <int N>
struct Factorial {
enum { value = N * Factorial<N - 1>::value };
};
template <>
struct Factorial<0> {
enum { value = 1 };
};
복잡해 보이지만 위에서 보였던 wrapper
와 크게 다르지 않다.
int val = Factorial<2>::value;
이런 코드가 나타난 순간 컴파일러는 다음과 같은 코드를 생성한다.
struct Factorial<2> {
enum { value = 2 * Factorial<1>::value };
};
struct Factorial<1> {
enum { value = 1 * Factorial<0>::value };
};
struct Factorial<0> {
enum { value = 1 };
};
Factorial<0>
은 더 이상 Factorial<N-1>
을 요구하지 않기 때문에 코드는 Factorial<0>
에서 생성을 멈춘다.
using 키워드
C++11부터 typedef와 비슷한 문법이 추가되었다.
using MY_TYPE = int;
MY_TYPE var;
typedef
와 소소한 차이점이 존재하지만 이 포스트에서는 중요하지 않으니 넘어간다.
decltype 키워드
decltype
키워드는 표현식(expression)의 타입을 추론할 때 사용한다.
int a = 1, b = 3;
decltype(a+b+1LL) var = a+b;
이는 결국 아래와 같은 코드가 된다.
int a = 1, b = 3;
long long var = a+b;
컴파일 타임에 타입추론이 가능한 표현식이라면 얼마든지 사용가능하며, 비슷한 성격의 auto
보다 더 유연한 활용이 가능하다.
주로 리턴하는 함수의 반환 타입을 결정할 때 사용된다.
template<typename T>
auto foo (T a) -> decltype(a+1LL) {
//...
}
원하는 만큼 별을 만들자
대략적으로 이렇게 계획을 세워 볼 수 있다.
- 템플릿 메타 프로그래밍으로 T**… 를 반환하는 함수 foo 만들기
- using type = decltype(foo()); 으로 사용하기 편하게 만들기
위에서 설명한 문법들을 활용하면 충분히 만들 수 있다.
#define MULTISTAR_POINTER(T, D) decltype(multistar_pointer<T, D>::compile())
template<typename T, std::size_t D>
struct multistar_pointer {
static constexpr auto compile() -> MULTISTAR_POINTER(T, D-1)* {
return NULL;
}
using type = MULTISTAR_POINTER(T, D);
};
template<typename T>
struct multistar_pointer<T, 1> {
static constexpr T* compile() {
return (T*)NULL;
}
using type = T*;
};
multistar_pointer<T, D>::compile
함수는 별이 D개 붙어있는 포인터를 리턴한다.
return 타입을 결정하기 위해서는 어떻게 해야할까?
Factorial
예시와 비슷하게, multistar_pointer<T, D-1>::compile
함수를 가져다 쓰자.
우리가 multistar_pointer<T, 1>::compile()
만 잘 만들었다면, decltype(multistar_pointer<T,D-1>::compile())*
에 우리가 원하는 결과가 담길 것이다.
이 코드에서는 필요한 decltype(multistar_pointer<T, D>::compile)
이 너무 길어서 매크로를 정의하고 사용하였다.
multistar_pointer<vector<int>, 20>::type vec20pt;
auto str100pt = multistar_pointer<string, 100>::compile();
MULTISTAR_POINTER(size_t, 3) sz3pt;
다양한 방식으로 변수를 만들 수 있다.
std::is_same
을 이용해서 더 정확히 확인해 볼 수도 있다.
int main(void) {
cout << boolalpha;
cout << is_same<int, int>::value << endl;
// true
cout << is_same<int***, multistar_pointer<int, 3>::type>::value << endl;
// true
}
이게 왜 필요한지 묻지 마세요… 저도 모르겠어요