Software Workshop

정수값 배열을 정수 연속(integer sequence) 클래스로 선언하여 사용할 수 있다.

정수 연속을 선언하는 방법은 integer_sequence<타입, 쉼표로 구분된 연속된 리터럴>()인데, 아래 예제에서는 다음 코드가 이에 해당한다.

아주 간단한 예제를 살펴보겠다.

#include <iostream>
#include <initializer_list>
#include <utility>

using namespace std;

template <typename T, T... I>
void print_sequence(integer_sequence<T, I...>) 
{
    initializer_list<bool>{ bool(cout << I << ' ')... };
    cout << endl;
}

int main(void)
{
    print_sequence(integer_sequence<int, 1, 2, 3>());
    print_sequence(integer_sequence<char, 'f', 'o', 'o'>());
    
    return 0;
}
1 2 3
f o o

print_sequence() 함수는 integer_sequence 타입의 객체를 파라미터로 받을 수 있고, integer_sequence 타입은 템플릿을 사용하고 있어서 임의의 타입의 원소들로 구성할 수 있으며(integer_sequence<T, ) 원소들의 갯수도 임의의 갯수가 될 수 있다.(, I...>) 이렇게 템플릿 차원에서 가변적인 형태인(template <typename T, T... I>) 것을 "variadic template"이라고 부른다. 좀 더 정확한 용어를 사용하자면, 템플릿 선언 중에 T...I 형태로 나오는 것을 "(template) parameter pack"이라고 하며 variadic template을 선언하기 위해 가변 템플릿 파라미터를 사용하는 것을 의미한다.

정수 연속을 출력하는 방법은 좀 일반적이지 않아서 다른 형태로 변환해서 처리해야 하는데, 여기서는 표준 라이브러리에 정의된 initializer_list 클래스를 이용하고 있다. 이 클래스는 proxy 객체로서 배열이나 벡터, 리스트에 들어있는 객체에 대한 접근을 제공하는 역할을 하고 있다. initializer_list<bool> 타입의 클래스 인스턴스를 생성하면서 중괄호 블럭에서 초기화를 해주고 있는데, cout을 실행한 결과값을 가지고 초기화를 하는 거라서 실제로는 원소마다 cout 연산을 수행하는 사실 상의 map 함수처럼 기능하고 있다.

다음은 이걸 약간 확장한 예제이다.

#include <iostream>
#include <initializer_list>
#include <utility>

using namespace std;

template <typename T, T... I>
void print_sequence(integer_sequence<T, I...>) {
    initializer_list<bool>{ bool(cout << I << ' ')... };
    cout << endl;
}

template <int offset, typename T, T... I>
void print_offset_sequence(integer_sequence<T, I...>) {
    print_sequence(integer_sequence<T, T(I + offset)...>());
}

int main() {
    print_sequence(make_index_sequence<10>());
    print_sequence(make_integer_sequence<short, 10>());
    print_offset_sequence<'A'>(make_integer_sequence<char, 26>());
}  

integer_sequence 클래스 대신에 직접 생성함수인 make_index_sequence<N>() 또는 make_integer_sequence<T, N>()를 사용하고 있다. 함수 이름이 비슷하니 주의가 필요하다. 이 함수들을 이용하면 Python의 range() 함수처럼 0부터 하나씩 늘어나는 N개의 정수 연속을 쉽게 생성할 수 있다. 위 예제의 실행 결과를 살펴보겠다.

0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

첫번째로 사용된 make_index_sequence<N>()는 정수값을 0부터 10개를 생성하므로 0~9까지 연속적으로 출력된다.
두번째로 사용된 make_integer_sequence<T, N>()은 타입을 short으로 지정했고 결과는 동일하다.
세번째는 char 타입의 정수값 26개를 생성하는데 그대로 출력하지 않고 offset이라는 추가 변수를 이용하여 정수값에 'A'만큼 더해서 알파벳 문자로 출력하도록 하고 있다.

이번에는 좀 더 어려운 예제를 분석해보겠다.

#include <algorithm>
#include <array>
#include <iostream>
#include <iterator>
#include <string>
#include <utility>

using namespace std;

template <typename T, size_t... I>
array<T, sizeof...(I)> make_array(T const& value, index_sequence<I...>) {
    return array<T, sizeof...(I)>{ (I, value)... };
}

template <int N, typename T>
array<T, N> make_array(T const& value) {
    return make_array(value, make_index_sequence<N>());
}

int main(void)
{
    auto array = make_array<20>(string("value"));
    copy(array.begin(), array.end(),
              ostream_iterator<string>(cout, " "));
    cout << endl;

    return 0;
}
value value value value value value value value value value value value value value value value value value value value

애플리케이션 코드의 의도는 단순하다. "value"라는 문자열을 20회 반복 출력하면서 구분자로 공백을 사이사이에 덧붙이는 것뿐이다. 그러니 make_array() 함수 내부에서 정수 연속을 이용해서 array<T, sizeof...(I)>을 생성하는 로직을 집중적으로 살펴보는 게 더 중요하다.

이 예제는 문자열 배열을 생성하는 함수 make_array()를 정의했고 다시 같은 이름의 make_array()를 정의했기 때문에 다소 혼란스러울 수 있다. 아랫쪽의 make_array(T const& value)가 먼저 호출되고 그게 윗쪽의 make_array(T const& value, index_sequence<I...>)를 호출하고 있다.

처음 호출된 make_array(T const& value)는 반복 시행 횟수 N과 문자열 리터럴 value을 넘겨받아서 직접 생성하지 않고 make_index_sequence<N>()를 호출한다. 그러면 0부터 19까지 20개의 정수값을 가지는 정수 연속이 생성된다. 이걸 make_array(T const& value, index_sequence<I...>)가 받아서 array<> 클래스를 생성하면서 초기화한 다음에 main() 함수까지 반환한다.