C++/[ecourse] C++ Template
5. 템플릿 기본 문법 - typename, template
헛둘이
2022. 9. 28. 11:05
본 글은 코드누리의 Template Programming 강좌를 개인 학습 목적으로 정리한 글 입니다.
https://www.ecourse.co.kr/course-status/
typename 키워드에 대한 개념
#include <iostream>
#include <boost/type_index.hpp>
using namespace std;
using namespace boost::typeindex;
class Test
{
public:
enum {value1 = 1};
static int value2;
typedef int INT;
using SHORT = short;
class InnerClass {};
};
int Test::value2 = 3;
int main()
{
Test::INT i = 3;
Test::SHORT s = 5;
int a = Test::value1;
Test::value2 = 4;
Test::InnerClass ic;
}
- Test에 ::로 접근할 수 있는 요소의 종류로는 아래와 같다.
- enum 상수
- 정적 변수
- typedef로 재정의한 타입
- using으로 재정의한 타입
- inner class
- 이 요소들을 공통점으로 묶어 둘로 나눌 수 있음
- 값 계열
- 타입 계열
T::DWORD* p; // 이렇게 적으면 컴파일러는 값으로 해석함
// T::DWORD가 값일 수도 있고 타입일 수도 있다.
typedef int DWORD; - 타입으로 해석
enum { DWORD = 5 };- 값으로 해석
// 타입으로 해석하려면 앞에 typename을 붙여주어야 한다.
typename T::DWORD* p
// 마이크로소프트 컴파일러는 에러가 안나는데, 반환값에 T::DWORD를 적으면 에러가 난다.
// 그래서 명시적으로 반환값에도, 함수 내에도 typename을 적어주는게 맞다.
템플릿이 아닌 경우에는 typename을 붙이면 안됨
(타입을 직접 적으면 컴파일러가 판단할 수 있음 - Test::DWORD* p 이런 경우)
value_type 키워드에 대한 개념
template<typename T>
void print_first_element(std::vector<T>& v)
{
int n = v.front();
std::cout << n << std::endl;
}
int main()
{
std::vector<double> v = {1,2,3};
print_first_element(v);
return 0;
}
- 그런데 print_first_element에서 vector가 아닌 list를 받는다면?
template<typename T>
void print_first_element(T& v)
{
int n = v.front();
std::cout << n << std::endl;
}
- 모든 타입의 컨테이너를 받는 T로 변경
- 그럼 아랫줄의 n의 타입은 뭘로 변경해야 할까?:
- 모든 컨테이너는 자신이 저장하는 타입이 있고 타입을 value_type이라는 이름으로 알려준다.
- 따라서 T::value_type이라고 적어주면 됨 (list<int>에서 int를 표기)
그런데 요샌 c++11에선 auto라는 문법으로 쉽게 해결 가능함
하지만 후술하는 부분에서 필요한 곳이 또 있음
template<typename T>
class Vector
{
T* buff;
int size;
public:
Vector(int sz, T value) {}
//리스트를 받는 복사 생성자를 만들기 위해서
template<typename C> Vector(C c) {}
};
template<typename C>
Vector(C c)->Vector<typename C::value_type>;//list의 타입을 받기 위해 C::value_type을 넣어줘야 함
int main()
{
Vector v(10, 3);
std::list s = { 1,2,3 };
Vector v2(s);
return 0;
}
- 리스트를 받는 복사 생성자에서 T의 타입을 C의 타입으로 받겠다는 디덕션 가이드임
template 키워드에 대한 개념
class Test
{
public:
template<typename T> static void f() {}
template<typename T> class Complex {};
};
template<typename T> void foo(T a)
{
Test::f<int>(); // ok
//T::f<int>(); //error
T::template f<int>();
// 앞에 template이라고 붙여서 꺾새가 템플릿의 인자를 보내주는 것임을 알려주어야 함
//T::Complex<int> c2;
typename T::template Complex<int> c2;
// 컴파일러는 이 꺾새가 뭔지 모름
// 그래서 꺾새가 뭔지 컴파일러에게 설명해주기 위해 T:: 뒤에 template라고 적어주어야 함
// Complex는 타입의 이름이면서 템플릿이다.
}
int main()
{
Test t;
foo(t);
return 0;
}
- 타입 T 안에 있는 템플릿에 접근하기 위해서 사용하는 키워드
- 타입 T 안의 함수 f는 템플릿 함수이기 때문에 T::template f<int>라고 알려주어야 함
- T::f<int>라고 해버리면 <int>을 대소 비교 연산자로 해석해서 오류가 남
template parameter에 대한 개념
- 템플릿 인자로 받을 수 있는 것엔 총 3종류가 있다.
- 타입 - 위 사용했던 것처럼 <typename T>와 같이 전달하는 것
- 값 - 바로 아래 후술
- 템플릿 - 값 다음에 후술
템플릿 인자로 받을 수 있는 값
- 정수
- enum 상수
- 포인터
- 함수 포인터
- auto (C++17부터)
#include <iostream>
#include <vector>
#include <string>
#include <list>
enum Color { red = 1, green = 2 };
//템플릿 파라미터에 값으로 들어갈 수 있는 것들
// 1.정수
template<int N> class Test1{ };
// 2. enum 상수
template<Color> class Test2 {};
// 3. 포인터
template<int*> class Test3 {};
// 4. 함수 포인터
template<int (*)(int)> class Test4{};
// 그 밖에 멤버 함수 포인터도 가능
// c++17부터는 auto도 가능
template<auto N> struct Test5
{
Test5()
{
std::cout << typeid(N).name() << std::endl;
}
};
int x = 0;
int main()
{
int n = 10;
Test1<10> t1;
//Test3<&n> t4; // 지역변수의 주소는 안됨
Test3<&x> t5; // 전역 변수의 주소는 가능
Test5<&main> m;
return 0;
}
- 지역변수의 경우 인자로 쓰일 수 없는데 no linkage와 external linkage 이 개념들에 대한 이해가 필요하다고 하셨다.
템플릿 인자로 받는 템플릿
template<typename T> class list {};
template<typename T, template<typename> class C> class Stack {}; // 두 번째 인자로 템플릿을 받는 예제
- Stack의 두 번째 인자로 다른 컨테이너를 받는 것
- 두 번째 인자의 형태인 template<typename> class C가 바로 위의 list 선언부와 흡사하므로 그렇게 기억하면 됨