Programming/_C++

_C++_Template

Player_blue 2023. 9. 24. 23:20

템플릿은 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가 결정되어야 함