원시 자료형이나 템플릿이 없는 클래스 자료형을 template<typename T>와 같이 받을 수 있다.
#include<iostream>
template<typename T>
T add(T a, T b) {
return a + b;
}
class Complex {
double r, i;
public:
Complex(double _r = 0.0, double _i = 0.0) :r(_r), i(_i) {}
friend Complex operator+(const Complex& a, const Complex& b) {
Complex c;
c.r = a.r + b.r;
c.i = a.i + b.i;
return c;
}
friend std::ostream& operator<<(std::ostream& os, const Complex& c) {
os << c.r << "\t" << c.i;
return os;
}
};
int main() {
std::cout << add(5, 7) << std::endl; // int 가능
std::cout << add(Complex(2.0, 1.0), Complex(3.0, 5.0)) << std::endl; // Complex 가능
return 0;
}
템플릿 인자를 받는 클래스의 경우 아래와 같이 작성이 가능하다. add 함수를 호출할 때 처럼 타입을 지정하면 타입이 있는 클래스가 되므로 호출하는데 문제가 없다.
template<typename Real>
class Complex {
Real r, i;
public:
Complex(Real _r = 0.0, Real _i = 0.0) :r(_r), i(_i) {}
friend Complex operator+(const Complex& a, const Complex& b) {
Complex c;
c.r = a.r + b.r;
c.i = a.i + b.i;
return c;
}
friend std::ostream& operator<<(std::ostream& os, const Complex& c) {
os << c.r << "\t" << c.i;
return os;
}
};
int main(){
std::cout << add(Complex<float>(2.0, 1.0), Complex<float>(3.0, 5.0)) << std::endl; // Complex 가능
}
Complex를 반환하는 함수를 작성할 경우 아래와 같이 작성하면 된다.
template<typename Real>
Complex<Real> GetRandomComplex() {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<Real> rand_real(0, 1);
Complex<Real> ret;
ret.r = rand_real(gen);
ret.i = rand_real(gen);
return ret;
}
int main() {
std::cout << GetRandomComplex<float>() << std::endl;
return 0;
}
이제 위의 GetRandomComplex를 GetRandomClass로 바꾸어 원하는 클래스와 연산 타입을 지정해 결과값을 받고자 한다.
첫번째 방법은 템플릿 인수를 2개를 받는 방법이다. 클래스와 연산타입을 모두 받아서 해당 인자들을 조합하여 자료형을 만드는 방법이고,
두번째 방법은 템플릿 인수를 1개를 받는 방법이다. 이는 이미 조합된(완성된) 클래스를 인자로 받아 사용한다.
template<typename Real>
class Complex {
public:
using Type = Real; //이미 템플릿으로 생성된 클래스의 템플릿 인수를 알아오기 위해 사용
Real r, i;
Complex(Real _r = 0.0, Real _i = 0.0) :r(_r), i(_i) {}
friend Complex operator+(const Complex& a, const Complex& b) {
return Complex(a.r + b.r, a.i + b.i);
}
friend std::ostream& operator<<(std::ostream& os, const Complex& c) {
os << c.r << "\t" << c.i;
return os;
}
};
template<typename Real>
class Fraction {
public:
using Type = Real;
Real n, d; // numerator, denominator;
Fraction(Real _n = 0.0, Real _d = 0.0) :n(_n), d(_d) {}
friend Fraction operator+(const Fraction& a, const Fraction& b) {
return Fraction(a.n * b.d + a.d * b.n, a.d * b.d);
}
friend std::ostream& operator<<(std::ostream& os, const Fraction& c) {
os << c.n << "/" << c.d;
return os;
}
};
template<template<typename Real> typename Class, typename Real>
Class<Real> GetRandomClass() {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<Real> rand_real(0, 1);
Class<Real> ret(rand_real(gen), rand_real(gen));
return ret;
}
template<typename ClassWithType>
ClassWithType GetRandomClass2() {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<ClassWithType::Type> rand_real(0, 1);
ClassWithType ret(rand_real(gen), rand_real(gen));
return ret;
}
int main() {
std::cout << GetRandomClass<Fraction, float>() << std::endl;
std::cout << GetRandomClass<Fraction, double>() << std::endl;
std::cout << GetRandomClass<Complex, float>() << std::endl;
std::cout << GetRandomClass<Complex, double>() << std::endl;
std::cout << GetRandomClass2<Fraction<float>>() << std::endl;
std::cout << GetRandomClass2<Fraction<double>>() << std::endl;
std::cout << GetRandomClass2<Complex<float>>() << std::endl;
std::cout << GetRandomClass2<Complex<double>>() << std::endl;
return 0;
}
예를 들어 1~n 까지의 숫자가 담긴 컨테이너를 반환하는 함수가 있다고 하자.
이 함수는 std::vector로 결과를 반환할 수도 있고 std::deque도 가능하다. 물론 std::set도 가능해야 한다.
그런데 2번에서 제안한 방법은 문제가 하나 있다.
바로 std::vector와 std::set은 템플릿 인자 개수가 다르다는 것이다.
template < class T, class Alloc = allocator<T> > class vector; // 템플릿 인자 2개
template < class T,
class Compare = less<T>,
class Alloc = allocator<T>
> class set; // 템플릿 인자 3개
? 디폴트 인자가 있는데 잘 동작하지 않을까? 라는 생각을 할 수 도 있다.
결론부터 말하면 동작하지 않는다. 템플릿 인자는 추가적인 템플릿 인자를 가질 수 없다.
아래 코드에서 typename Container는 말 그대로 완성된 자료형이여야 한다.
Wrong Code
template<typename Container> Container<int> GetValues(int n) { // Error: Container는 새로운 템플릿 인자를 가질 수 없다. Container<int> c; for (int i = 1; i <= n; i++) { c.insert(c.end(), i); } return c; }
그럼 템플릿 인자를 가질 수 있도록 구현하면 어떤가?
여전히 문제가 있다. 템플릿의 인자 개수는 엄격하기 때문에 컴파일 에러가 난다.
Wrong Code
template<template<typename T> typename Container> Container<int> GetValues(int n) { Container<int> c; for (int i = 1; i <= n; i++) { c.insert(c.end(), i); } return c; } int main() { auto a = GetValues<std::vector<int>>(5); // Error: std::vector는 템플릿 인자가 1개가 아니다. 엄격히 구분한다. return 0; }error C3208: 'GetValues': 클래스 템플릿 'std::vector'에 대한 템플릿 매개 변수 목록이 template 템플릿 매개 변수 'Container'에 대한 템플릿 매개 변수 목록과 일치하지 않습니다.
이제 std::vector의 템플릿 인자 개수에 맞춰서 코딩 해보자.
아래 코드는 정상적으로 동작한다. 그러나 std::set에서는 동작하지 않는다.
(왜 push_back으로 안쓰고 insert로 했는지 이제 알아야 한다.)
그리고 2번째 템플릿 인자를 직접 넣어야 하는것도 아름답지 않다.
Not recommended Code
template<template<typename T,typename K> typename Container> Container<int,std::allocator<int>> GetValues(int n) { Container<int, std::allocator<int>> c; for (int i = 1; i <= n; i++) { c.insert(c.end(), i); } return c; } int main() { auto res = GetValues<std::vector>(5); for (auto& e : res) { std::cout << e << std::endl; } return 0; }1 2 3 4 5
방법은 있다. 가변 템플릿 인자를 사용하면 모든것이 해결된다.
template<template<typename...Args> class Container>
Container<int> GetValues(int n) {
Container<int> c;
for (int i = 1; i <= n; i++) {
c.insert(c.end(), i);
}
return c;
}
int main() {
auto res1 = GetValues<std::vector>(5);
for (auto& e : res1) std::cout << e << std::endl;
auto res2 = GetValues<std::set>(5);
for (auto& e : res2) std::cout << e << std::endl;
auto res3 = GetValues<std::list>(5);
for (auto& e : res3) std::cout << e << std::endl;
auto res4 = GetValues<std::deque>(5);
for (auto& e : res4) std::cout << e << std::endl;
return 0;
}