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에 ::로 접근할 수 있는 요소의 종류로는 아래와 같다.
  1. enum 상수
  2. 정적 변수
  3. typedef로 재정의한 타입
  4. using으로 재정의한 타입
  5. inner class

 

  • 이 요소들을 공통점으로 묶어 둘로 나눌 수 있음
  1. 값 계열
  2. 타입 계열

 

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종류가 있다.
  1. 타입 - 위 사용했던 것처럼 <typename T>와 같이 전달하는 것
  2. 값 - 바로 아래 후술
  3. 템플릿 - 값 다음에 후술

 

템플릿 인자로 받을 수 있는 값
  1. 정수
  2. enum 상수
  3. 포인터
  4. 함수 포인터
  5. 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 선언부와 흡사하므로 그렇게 기억하면 됨