본문 바로가기
NC University/Advanced C++

5일차 - 가변인자 템플릿 (variadic template) (1)

by 날쑤 2015. 8. 12.

Variadic template

  가변인자 템플릿은 템플릿의 타입 인자의 종류 및 개수가 가변적인 것을 말하며, 이는 C++ 11의 중요한 특징중 하나이다. 우선 가변인자 템플릿을 아주 심플하게 사용하는 예를 살펴보면서 기초적인 내용 정리를 해보자.

template <typename ... Types> 
class Test {};  

template <typename ... Types> 
void foo (Types ... args) {}
  
int main(int argc, char* argv[]) {   
	foo(1, 3.4, "a");   
	Test<int, double> t;    
	
	return 0; 
}

  Types는 여러 개의 '타입'을 나타내며, 주로 'Types', 'ARGS' 등의 단어를 관례적으로 많이 사용한다. 그리고 args는 Types에 부합하는 타입 객체들의 집합으로서, "parameter pack"이라고 부른다.

  문법적으로 특이한 부분은 템플릿의 타입 인자를 선언하고 사용하는 부분이다, 타입을 선언할 때에는 Types의 앞에 '...' 이 오지만 사용할 때에는 Types 뒤에 '...' 이 온다. 또한, 가변인자 템플릿의 타입 인자는 타입의 개수 뿐만 아니라 종류에도 제약이 없다. 이제는 가변인자 템플릿을 선언 이후에 어떤 식으로 사용하는지를 살펴보자.

#include <iostream> 

using namespace std;  

int f (int a) { return a+1; }  

template <typename ... Types>  
void goo (Types ... args)  {   
	cout << "goo" << endl; 
}  

void hoo (int a, int b, int c) {   
	cout << "hoo " << a << b << c << endl; 
}  

template <typename ... Types>  
void foo (Types ... args) {   
	// 1. sizeof...() 키워드   
	cout << sizeof...(Types) << endl;   
	cout << sizeof...(args) << endl;    
	
	// 2. parameter pack을 다른 함수로 보내는 코드 (...이 이름 뒤에 따라 붙음)   
	goo(args...);    
	
	// 3. pack expansion - parameter pack에 있는 내용을 풀어서 전달   
	hoo(args...);   
	hoo(f(args)...); //< hoo(f(1), f(2), f(3)) 
}   

int main(int argc, char* argv[]) {   
	foo(1, 2, 3);   
	return 0; 
}

  위 코드에 대한 실행결과는 다음과 같다.

  우선 sizeof... 키워드는 실행결과를 통해 의미를 명확히 알 수 있다. 기존의 sizeof가 타입의 크기(bytes)를 의미했다면 가변인자 템플릿에서의 sizeof...는 타입 인자의 갯수(혹은 parameter pack에 있는 객체의 수)를 뜻한다. 즉, 컴파일 타임에 parameter pack 내의 객체의 수를 계산해야 할 필요가 있다면, 바로 sizeof...가 해답이 될 것이다.

  다음으로는 parameter pack을 다른 함수로 전달하는 방법 두 가지가 등장한다. 하나는 parameter pack 자체를 통채로 전달하는 것이고, 다른 하나는 paramter pack에 있는 객체를 풀어서 전달하는 방법이다. 전자는 호출되는 함수의 파라메터도 가변 인자일 경우이며, 후자는 args 내의 객체 갯수와 전달받는 함수의 파라메터의 갯수가 일치하는 경우로서 특별히 이를 pack expansion이라고 부른다. 참고로 pack expansion 시에는 foo 함수의 마지막 라인처럼 각 인자에 대해 동일한 연산을 적용해서 다른 함수로 전달할 수도 있다.

가변인자 꺼내기

  Variadic template에서 가장 핵심은 paramter pack에서 객체를 꺼내는 것이다. 여기서 명심해야 할 것은 C의 가변인자 함수와 마찬가지로 가변인자 템플릿도 모든 템플릿 인자가 가변인자일 경우에는 인자를 꺼낼 방법이 없다는 점이다.

// 1. 가변인자 함수 
void Foo (...);				//< 몇 개의 인자가 넘어왔는지 알 수 없다. 
void Foo (int count, ...);	//< count를 이용해서 가변인자 리스트를 초기화   

// 2. 가변인자 템플릿 
template <typename ... Types> 
void Goo (Types ... args) {}       //< 모든 템플릿 인자가 가변인자면 객체를 꺼낼 수 없다.  

template <typename T, typename ... Types>  
void Goo (T a, Types ... args) {}  //< 한 개는 정확한 타입으로 받아야 한다.

 

  함수가 어떤 식으로 구성되어야 하는지를 알았으니, 이를 바탕으로 parameter pack으로부터 객체를 꺼내쓰는 법을 알아보자.

#include <iostream> 

using namespace std;  

template <typename T, typename ... Types>  
void Goo (T a, Types ... args) {   
	static int n = 0;   
	++n;      
	cout << n << ": " << typeid(T).name() << " : " << a << endl;    // 꺼낼 때는 'recursive'한 방법으로 꺼내야 한다.   
	
	// recursive한 방식으로 동작하므로 작은 코드에 대해서만 사용해야한다.   
	Goo(args...); 
}  

// 재귀의 종료를 위해 인자없는 Goo 함수가 필요하다. 
void Goo () {  cout << "Goo 종료" << endl;  }  

int main(int argc, char* argv[]) {   
	Goo(1, 3.4, "aa", 5);   
	return 0; 
}

 

  우선 main 함수에서 Goo(1, 3.4, "aa", 5)가 호출되었다. 인자들은 Goo의 선언에 따라 첫번째 인자인 1은 a로, 나머지 3개의 인자는 parameter pack(args)으로 전달된다. Goo 함수 내에서는 parameter pack을 인자로 Goo를 재귀호출한다. 이렇게 쭉 내려가다보면 마지막에 인자가 없는 Goo가 호출되므로 이를 위한 overloading이 필요하다.

  한편, 위의 코드를 출력해보면 타입 앞의 정수 값이 의도와 달리 다 1로 출력됨을 확인할 수 있다. 이는 Goo 함수는 파라메터 개수별로 다른 함수가 만들어지고, n은 생성된 각 함수마다 따로 존재하기 때문이다. 그러므로 이를 정상적인 의도대로 출력하려면 n을 함수 내부가 아니라 전역 범위로 선언해야 한다. 이를 수정하고 최종적인 실행결과를 확인해보면 아래와 같은 결과를 얻을 수 있다.

  이것으로 가변인자 템플릿과 관련된 기본적인 내용들을 어느정도 정리했다. 이제 가변인자 템플릿을 좀 더 실제적으로 써먹는 예제들을 살펴보자.

'NC University > Advanced C++' 카테고리의 다른 글

5일차 - 우측값 (R-value)  (0) 2015.08.24
5일차 - 가변인자 템플릿 (variadic template) (2)  (0) 2015.08.21
5일차 - range-based for loop  (0) 2015.08.11
5일차 - begin & end  (0) 2015.08.10
5일차 - initializer_list  (0) 2015.08.05