_C++_Template
템플릿은 C++ 의 일반화된 코드를 남드는 강력한 도구이다.
대상에 대한 타입만 다르고 로직이 다르지 않다면 템플릿으로 단순한 반복 작업으로 함수나 클래스를 만들 수 있다.
예를 들어 int type에 대한 더하기 함수, double type에 대한 더하기 함수 두가지가 있다면, 템플릿으로 형태를 만들어서 두 형식의 함수를 만들 수 있다.
기본적인 구조는 다음과 같다.
template<typename T>
class Samlple {
Sample(const Sample<T>& src) = default; //const Sample<T>&는 파라미터 타입의 예시
//...
};
//함수 템플릿
template<typename T>
void f(const T& param); //const T&는 파라미터 타입의 예시
T는 아직 정해지지 않는 타입이며, T는 컴파일시 타입을 추론한다.
함수 템플릿 예시를 보면 다음과 같다.
#include <iostream>
using namespace std;
template <typename T>
T sum(T a, T b){ // T는 아직 정해지지 않은 type
return a + b;
}
int main(){
int a = 3, b = 5;
cout << a + b << "\n"; // 8
double c = 3.2, d = 5.1;
cout << c + d << "\n"; // 8.3
return 0;
}
컴파일하는 순간 일련의 알고리즘으로 T가 무엇인지 추론하는데, 이는 auto 타입의 추론과 유사하다.
템플릿 클래스나 템플릿 함수가 인스턴스화 되는 시점에 컴파일러는 그 구현부를 볼 수 있어야 하므로 템플릿의 정의는 헤더 파일에 들어가야한다. 관례적으로 이렇게 템플릿 정의가 포함된 헤더파일의 확장자를 hpp로 사용한다.
보충 내용: 템플릿 파라미터 팩, 논타입 템플릿 파라미터, 템플릿 템플릿 파라미터
템플릿 파라미터는 한 번에 여러 개의 파라미터가 올 수 있게 만들 수 있다.
템플릿 파라미터 팩을 사용하면 개수의 제한이 없이 파라미터를 정의할 수 있다.
#include<cstdio>
#include<chrono>
template<typename Func, typename... Params>
// 템플릿 팩을 사용하여 여러 개의 파라미터를 정의하는 모습.
auto runAndReturnElapsedTime(Func&& function, Params&&... params)
{
auto start = std::chrono::high_resolution_clock::now();
std::forward<decltype(function)>(function)(std::forward<decltype(params)>(params)...);
return (std::chrono::high_resolution_clock::now() - start).count();
}
auto runAndReturnElapsedTime2 = [](auto&& function, auto&&... params)
{
auto start = std::chrono::high_resolution_clock::now();
std::forward<decltype(function)>(function)(std::forward<decltype(params)>(params)...);
return (std::chrono::high_resolution_clock::now() - start).count();
};
int main()
{
auto times = runAndReturnElapsedTime(printf, "%d %d\n", 1, 2);
printf("%lld ns\n", times);
auto times2 = runAndReturnElapsedTime2(printf, "%d %d\n", 1, 2);
printf("%lld ns\n", times2);
}
템플릿 파라미터는 정수형 타입 값, 열거형 타입 값, 포인터 값, 참조형 변수등이 올 수 있으며, 이를 non-type 템플릿 파라미터라고 한다. (non-type 템플릿 파라미터는 디폴트 값을 줄 수 있음.)
템플릿 파라미터에 다른 템플릿이 올 수 있는데 이를 템플릿 템플릿 파라미터라 한다.
#include<iostream>
#include<vector>
using namespace std;
//템플릿 파라미터에 다른 템플릿이 들어가있는 모습.
template<
typename T,
template<typename E, typename Allocator = allocator<E>> class Container
>
class Grid {
size_t mWidth;
size_t mHeight;
Container<T>* mCells;
public:
Grid<T, Container>(size_t inWidth, size_t inHeight) : mWidth(inWidth), mHeight(inHeight)
{
mCells = new Container<T>[mWidth];
for (size_t i = 0; i < mWidth; ++i)
mCells[i].resize(mHeight);
}
~Grid()
{
delete[] mCells;
}
};
int main()
{
Grid<int, vector> vGrid(5, 5);
}
또한, using을 이용해서 별칭 템플릿을 만들 수 있다.
typedef를 사용한다면 타입이 결정된 템플릿 인스턴스에 대해서만 별칭을 만들 수 있고 템플릿 자체에 대한 별칭을 만들 수 없다.
template<typename T>
using MyAllocList = std::list<T, MyAlloc<T>>;
MyAllocList<Widget> lw; //std::list<Widget, MyAlloc<Widget>>과 같음
typdef std::list<T, MyAlloc<T>> MyAllocList2; //컴파일 에러: T가 결정되어야 함