template <typename T>
class Vector {
T* data;
int capacity;
// ...
아래에 정의되는 class에 대해 템플릿을 정의하고, 템플릿 인자로 T를 받게되며, T는 반드시 어떠한 type의 이름임을 명시하고 있음.
위의 경우 템플릿 문장 아래 오는 것이 class Vector이므로 이 Vector 클래스에 대한 템플릿 명시
Vector<int> int_vec;
정의한 템플릿의 인자에 값을 전달하기 위해서는, 위와 같이 <> 안에 전달하려는 것 명시해줘야 함. 1번째 줄의 경우 T에 int가 전달되는 것.
클래스 템플릿에 인자를 전달해서 실제 코드를 생성하는 것
Vector<int> // 혹은
Vector<std::string>
템플릿을 통해 인자로 타입 전달.
위와 같이 Vector의 템플릿의 인자에 타입을 전달하면, 컴파일러는 이것을 보고 코드 생성.
Vector <int>의 경우, 아래와 같이 T가 int로 치환된 코드가 생성되는 것.
class Vector {
int* data;
int capacity;
int length;
public:
// 어떤 타입을 보관하는지
typedef T value_type;
// 생성자
Vector(int n = 1) : data(new int[n]), capacity(n), length(0) {}
// 맨 뒤에 새로운 원소를 추가한다.
void push_back(int s) {
if (capacity <= length) {
int* temp = new int[capacity * 2];
for (int i = 0; i < length; i++) {
temp[i] = data[i];
}
delete[] data;
data = temp;
capacity *= 2;
}
data[length] = s;
length++;
}
// 임의의 위치의 원소에 접근한다.
int operator[](int i) { return data[i]; }
// x 번째 위치한 원소를 제거한다.
void remove(int x) {
for (int i = x + 1; i < length; i++) {
data[i - 1] = data[i];
}
length--;
}
// 현재 벡터의 크기를 구한다.
int size() { return length; }
~Vector() {
if (data) {
delete[] data;
}
}
};
Vector<int> int_vec;
즉, 위의 코드를 다시 보면 !
Vector의 T가 int로 치환된 클래스의 객체 int_vec를 생성하는 것.
Vector<bool>과 같이 일부 경우에 대해서 따로 처리하는 것을 템플릿 특수화 라고 함.
1bytebool형은 1bit로 저장 가능하므로, 메모리 낭비.// 나는 A가 int고 C가 double일 때 따로 처리하고 싶어!
template <typename B>
class test<int, B, double> {};
// 나는 A, B가 int고 C가 double일 때 따로 처리하고 싶어!
// 이와 같이 전달하는 템플릿 인자가 없더라도 특수화 하고 싶다면
// template<> 라도 남겨줘야 함.
template <>
class test<int, int, double> {};
// 따라서, bool 벡터의 경우 아래와 같이 처리
template <>
class Vector<bool> {
... // 원하는 코드
}
#include <iostream>
#include <string>
// 템플릿 함수 정의
template <typename T>
T max(T& a, T& b) {
return a > b ? a : b;
}
int main() {
int a = 1, b = 2;
std::cout << "Max (" << a << "," << b << ") ? : " << max(a, b) << std::endl;
std::string s = "hello", t = "world";
std::cout << "Max (" << s << "," << t << ") ? : " << max(s, t) << std::endl;
}
실제로 템플릿 함수가 인스턴스화 되는 부분
max<int>(a, b) 로 자동 해석max<string>(s, t)로 자동 해석함수는 아니지만 함수 인 척을 하는 객체를 함수 객체 (Function Object), 혹은 줄여서 Functor 라고 함
- 클래스 자체에 여러가지 내부
state를 저장해서 비교 자체가 복잡한 경우에도 손쉽게 사용자가 원하는 방식으로 만들어낼 수 있음- 함수포인터로 함수를 받아서 처리한다면 컴파일러가 최적화를 할 수 없지만, Functor 를 넘기게 된다면 컴파일러가 operator() 자체를 인라인화 시켜서 매우 빠르게 작업을 수행할 수 있음
template <typename Cont, typename Comp>
void bubble_sort(Cont& cont, Comp& comp) {
for (int i = 0; i < cont.size(); i++) {
for (int j = i + 1; j < cont.size(); j++) {
if (!comp(cont[i], cont[j])) {
cont.swap(i, j);
}
}
}
}
여기서 comp 는 함수가 아니라 객체 이고, Comp 클래스에서 () 연산자를 오버로딩함.
struct Comp1 {
bool operator()(int a, int b) { return a > b; }
};
struct Comp2 {
bool operator()(int a, int b) { return a < b; }
};
Comp1 과 Comp2 모두 아무 것도 하지 않고 단순히 operator() 만 정의하고 있음. 그리고 이 객체들은 bubble_sort안에서 함수인양 사용됨 = 함수객체
템플릿 인자로 타입만 받을 수 있는 것이 아님.
템플릿 인자로 전달할 수 있는 타입
- 정수 타입들 (bool, char, int, long 등등). 당연히 float 과 double 은 제외
- 포인터타입
- enum 타입
- std::nullptr_t (널 포인터)
타입이 아닌 템플릿 인자를 가장 많이 활용하는 예시는 컴파일 타임에 값들이 정해져야 하는 것들
대표적으로 배열.
템플릿 인자로 배열의 크기를 명시하여 함수에 배열을 전달할 때 배열의 크기 정보 전달
#include <iostream>
template <typename T, int num>
T add_num(T t) {
return t + num;
}
int main() {
int x = 3;
std::cout << "x : " << add_num<int, 5>(x) << std::endl;
}
template 의 인자로 T 를 받고, 추가적으로 마치 함수의 인자 처럼 int num 을 또 받고 있음. add_num 함수를 호출할 때 <> 를 통해 전달하는 인자들이 들어가게 됨.
add_num<int, 5>(x) // 이렇게 전달했으므로
int add_num(int t) { return t + 5; } // 생성되는 함수는 이와 같음
함수에 디폴트 인자를 지정할 수 있는 것처럼 템플릿도 디폴트 인자를 지정할 수 있음.
#include <iostream>
template <typename T, int num = 5>
T add_num(T t) {
return t + num;
}
int main() {
int x = 3;
std::cout << "x : " << add_num(x) << std::endl;
}
= (디폴트 값)을 넣어주면 됨.add_num(x) = add_num<int, 5>#include <iostream>
#include <string>
template <typename T>
struct Compare {
bool operator()(const T& a, const T& b) const { return a < b; }
};
template <typename T, typename Comp = Compare<T>>
T Min(T a, T b) {
Comp comp;
if (comp(a, b)) {
return a;
}
return b;
}
int main() {
int a = 3, b = 5;
std::cout << "Min " << a << " , " << b << " :: " << Min(a, b) << std::endl;
std::string s1 = "abc", s2 = "def";
std::cout << "Min " << s1 << " , " << s2 << " :: " << Min(s1, s2)
<< std::endl;
}

Compare<T> 가 전달되어서 < 를 통해 비교를 수행템플릿을 사용해서 임의의 개수의 인자를 받는 방법
#include <iostream>
template <typename T>
void print(T arg) {
std::cout << arg << std::endl;
}
template <typename T, typename... Types>
void print(T arg, Types... args) {
std::cout << arg << ", ";
print(args...);
}
int main() {
print(1, 3.1, "abc");
print(1, 2, 3, 4, 5, 6, 7);
}
```
```cpp
template <typename T, typename... Types>
typename 뒤에 ...으로 오는 것을 의미0개 이상의 템플릿 인자들을 나타냄앞에 ...이 옴 (?????)void print(T arg, Types... args) {
...으로 오는 것을 의미뒤에 ...이 옴print(1, 3.1, "abc");
C++ 컴파일러는 이 두 개의 print 함수 정의를 살펴보면서 어느 것을 택해야 할지 정해야 함. 첫 번째 print 의 경우 인자로 단 1 개만 받기 때문에 후보에서 제외되고 두 번째 print 가 택해짐.
template <typename T, typename... Types>
void print(T arg, Types... args) {
std::cout << arg << ", ";
print(args...);
}
T 는 int 로 추론되고, arg 에는 1 이 오게 됨. 그리고 args 에는 나머지 3.1 과 "abc" 가 오는 것!print(args...);에 print에 전달되었던 나머지 인자들이 오는 것** 참고
void print(T arg)이 void print(T arg, Types... args)보다 먼저 선언되어 있어야 함. void print(T arg, Types... args) 내의 print()가 앞의 함수에 정의되어 있기 때문에.#include <cstring>
#include <iostream>
#include <string>
size_t GetStringSize(const char* s) { return strlen(s); }
size_t GetStringSize(const std::string& s) { return s.size(); }
template <typename String, typename... Strings>
size_t GetStringSize(const String& s, Strings... strs) {
return GetStringSize(s) + GetStringSize(strs...);
}
// 재귀 호출 종료를 위한 베이스 케이스
void AppendToString(std::string* concat_str) { return; }
template <typename String, typename... Strings>
void AppendToString(std::string* concat_str, const String& s, Strings... strs) {
concat_str->append(s);
AppendToString(concat_str, strs...);
}
template <typename String, typename... Strings>
std::string StrCat(const String& s, Strings... strs) {
// 먼저 합쳐질 문자열의 총 길이를 구한다.
size_t total_size = GetStringSize(s, strs...);
// reserve 를 통해 미리 공간을 할당해 놓는다.
std::string concat_str;
concat_str.reserve(total_size);
concat_str = s;
AppendToString(&concat_str, strs...);
return concat_str;
}
int main() {
// std::string 과 const char* 을 혼합해서 사용 가능하다.
std::cout << StrCat(std::string("this"), " ", "is", " ", std::string("a"),
" ", std::string("sentence"));
}
GetStringSize 함수로 임의의 개수의 문자열을 받아서 각각의 길이를 더한 것들을 리턴reserve 함수를 통해서 필요한 만큼 미리 공간을 할당AppendToString 의 첫 번째 인자로는 합쳐진 문자열을 보관할 문자열을 계속 전달하고, 그 뒤로 합칠 문자열들을 인자로 전달sizeof 연산자는 인자의 크기를 리턴sizeof... 연산자는 전체 인자의 개수를 리턴#include <iostream>
// 재귀 호출 종료를 위한 베이스 케이스
int sum_all() { return 0; }
template <typename... Ints>
int sum_all(int num, Ints... nums) {
return num + sum_all(nums...);
}
template <typename... Ints>
double average(Ints... nums) {
return static_cast<double>(sum_all(nums...)) / sizeof...(nums);
}
int main() {
// (1 + 4 + 2 + 3 + 10) / 5
std::cout << average(1, 4, 2, 3, 10) << std::endl;
}
가변 길이 템플릿은 매우 편리하지만 한 가지 단점 존재. -> 재귀 함수 형태로 구성해야 하기 때문에, 반드시 재귀 호출 종료를 위한 함수를 따로 만들어야 한다는 것 -> C++ 17 에 새로 도입된 Fold 형식을 사용한다면 이를 훨씬 간단하게 표현 가능
#include <iostream>
template <typename... Ints>
int sum_all(Ints... nums) {
return (... + nums);
}
int main() {
// 1 + 4 + 2 + 3 + 10
std::cout << sum_all(1, 4, 2, 3, 10) << std::endl;
}

return (... + nums);
위의 코드가 Fold형식으로,
return ((((1 + 4) + 2) + 3) + 10);
컴파일러에서 이와 같이 해석됨. 이와 같은 형태를 단항 좌측 Fold (Unary left fold) 이라고 부름.

I는 초기값을 의미op자리에는 +, -, <, <<, -, >, ,와 같은 대부분의 이항연산자가 포함()로 감싸줘야 함.