일반화 프로그래밍: 템플릿을 통해 타입이 마치 인자인 것처럼 사용하는 것
/* 나만의 std::array 구현 */
#include <iostream>
template <typename T, unsigned int N>
class Array {
T data[N];
public:
// 배열을 받는 레퍼런스 arr
Array(T (&arr)[N]) {
for (int i = 0; i < N; i++) {
data[i] = arr[i];
}
}
T* get_array() {
return data;
}
unsigned int size() {
return N;
}
void print_all() {
for (int i = 0; i < N; i++) {
std::cout << data[i] << ", ";
}
std::cout << std::endl;
}
};
int main() {
int arr[3] = {1, 2, 3}; // 배열 wrapper 클래스
Array<int, 3> arr_w(arr);
arr_w.print_all();
}
그럼 Array<int, 5> Array<int, 3>
이 2개는 다른 클래스인가?
std::cout << (typeid(Array<int, 3>) == typeid(Array<int, 5>)) << std::endl;
// 출력: 0
다른 템플릿 인자로 인스턴스화되었기 때문에 다르다.
템플릿을 사용하면 객체를 안 생성해도 타입에 어떤 값을 부여할 수 있고, 또 그 타입들을 가지고 연산을 할 수 있음.
템플릿을 사용하면 컴파일 타임에 연산이 끝난다는 게 특징임.
따라서 다음과 같은 게 가능함.
#include <iostream>
#include <typeinfo>
template <int N>
struct Int {
static const int num = N;
};
template <typename T, typename U>
struct add {
typedef Int<T::num + U::num> result;
};
int main() {
typedef Int<1> one; typedef Int<2> two;
typedef add<one, two>::result three;
// 🥰 주목! 컴파일 타임에 3으로 치환됨
std::cout << "Addtion result : " << three::num << std::endl;
}
즉, three라는 result가 3으로 치환되는 시점이 컴파일될 때인 것이다.
프로그램 실행 속도를 향상시킬 수 있다.
모든 값들이 컴파일될 때 치환되기 때문이다(대신 컴파일 시간<프로그램 실행 속도).
버그를 찾는 게 힘듦.
복잡해서 프로그램 전체를 구현하기에 애매함
#include <iostream>
#include <typeinfo>
template <int X, int Y>
struct GCD {
static const int value = GCD<Y, X % Y>::value;
};
template <int X>
struct GCD<X, 0> {
static const int value = X;
};
template <int N, int D = 1>
struct Ratio {
typedef Ratio<N, D> type;
static const int num = N; // 분자
static const int den = D; // 분모
};
template <class R1, class R2>
struct _Ratio_add {
typedef Ratio<R1::num * R2::den + R2::num * R1::den, R1::den * R2::den> type; };
template <class R1, class R2>
struct Ratio_add : _Ratio_add<R1, R2>::type {};
int main() {
typedef Ratio<2, 3> rat;
typedef Ratio<3, 2> rat2;
typedef Ratio_add<rat, rat2> rat3;
std::cout << rat3::num << " / " << rat3::den << std::endl; return 0;
}
이와 같이 Ratio_add로 분자 분모를 계산해 분수의 계산을 할 수 있으며,
이를 더 확장하면 컴파일 타임에 유리수 사칙 연산을 계산할 수 있다.
의존 타입: 템플릿 인자에 따라서 어떠한 타입이 달라질 수 있는 것
아래 코드는 컴파일 오류 발생. 이는 divide<N, two>::result
가 값인 지 타입인 지 명시하지 않아서 발생하는 오류다.
template <int N>
struct INT {
static const int num = N;
};
template <typename a, typename b>
struct add {
typedef INT<a::num + b::num> result;
};
template <typename a, typename b>
struct divide {
typedef INT<a::num / b::num> result;
};
using one = INT<1>;
using two = INT<2>;
using three = INT<3>;
template <typename N, typename d>
struct check_div {
// result 중에서 한 개라도 true 면 전체가 true
static const bool result =
(N::num % d::num == 0) || check_div<N, add<d, one>::result>::result;
};
template <typename N>
struct is_prime {
static const bool result = !check_div<N, two>::result;
};
template <>
struct is_prime<two> {
static const bool result = true;
};
template <>
struct is_prime<three> {
static const bool result = true;
};
template <typename N>
// 🥰 여기가 문제!
struct check_div<N, divide<N, two>::result> {
static const bool result = (N::num % (N::num / 2) == 0);
};
따라서
truct check_div<N, typename divide<N, two>::result> {
이렇게 typename 키워드를 붙여주면 됨.
컴파일러가 타입을 정확히 알아낼 수 있는 경우 굳이 그 길고 긴 타입을 적지 않고 간단히 auto로 표현 가능함.
짧은 이름의 타입일 경우 그냥 써주는 게 좋음.