C++/레퍼런스(Reference)

C++ Reference: std::integer_sequence, std::index_sequence

kim선달 2020. 10. 29. 02:11
Select Language

std::integer_sequence, std::index_sequence

 

Can make integer sequence in compile-time, so it is often used in compile-time indexing.

컴파일 타임에 정수 시퀸스를 가진 녀석을 만들 수 있어, 컴파일 타임 인덱싱 등에 활용되는 녀석입니다.


<utility> 헤더에 정의되어 있는 클래스입니다.

Defined in header <utility>

template<typename T, T... Ints>
class integer_sequence;

template<std::size_t ...Ints>
using index_sequence = integer_sequence<std::size_t, Ints...>;

 

 

std::index_sequencestd::integer_sequence의 첫번째 템플릿 파라미터가 std::size_t 로 템플릿 특수화(template specialization) 된 클래스입니다. (C++에서는 인덱싱의 표준 인자가 std::size_t 이므로)

그래서 signed 타입과 연산할 때는 조심해야 합니다.

std::index_sequence is specialization of std::integer_sequence, with it's first type as std::size_t(Since C++ standard uses std::size_t for indexing).

So you should be aware of it when computing with signed numbers.

 

 

아래는 연속된 정수 시퀸스를 가진 std::integer_sequence를 얻는 Helper Template 입니다.

 

template<typename T, T N>
std::make_integer_sequence<T, N>  // 0, 1, 2, ... N-1 을 템플릿 인자로 갖는 std::integer_sequence 입니다

template<std::size_t N>
std::make_index_sequence<N> // 0, 1, 2, ... N-1 을 템플릿 인자로 갖는 std::index_sequence 입니다

template<typename ...Args>
std::index_sequence_for<Args...> // 0, 1, 2, ... sizeof...(Args)-1 을 템플릿 인자로 갖는 std::index_sequence 입니다.

Below is Helper Templates which can make incrementing integer sequence(0 to N-1)

 

template<typename T, T N>
std::make_integer_sequence<T, N>  // std::integer_sequence that gets 0, 1, 2, ... N-1 as a template parameter

template<std::size_t N>
std::make_index_sequence<N> // std::index_sequence that gets 0, 1, 2, ... N-1 as a template parameter

template<typename ...Args>
std::index_sequence_for<Args...> // std::index_sequence that gets 0, 1, 2, ... sizeof...(Args)-1 as a template parameter

 


예시 1

Example 1

 

template<typename T, std::size_t... I>
auto make_array(const std::vector<T>& vec, std::index_sequence<I...>){
  return std::array<T, sizeof...(I)>{vec[I]...};
}

template<typename T>
void print_container(const T& arr){
  for(const auto elem : arr)
    std::cout << elem << " ";
  std::cout << std::endl;
}

int main()
{
  std::vector<int> v = {1, 2, 3, 4, 5, -1, -2, -3, -4, -5};
  auto arr = make_array(v, std::make_index_sequence<3>());
  
  print_container(arr);
  print_container(make_array(v, std::index_sequence<1, 3, 5, 7>{}));
}

 

 

출력은

Outputs:

1 2 3 
2 4 -1 -3

 

std::make_index_sequence<3>() 은 0, 1, 2를 템플릿 파라미터로 갖는 std::index_sequence 객체를 생성합니다.

make_array 함수에서 인자로 들어온 std::index_sequence 의 템플릿 파라미터를 유추해서,

std::initializer_list 에 벡터의 각 원소를 unpack 한 후,

std::array 를 만든 뒤 리턴하는 모습입니다.

sizeof...() 연산자는 파리미터 팩의 타입 개수를 반환하는 연산자입니다.

 

std::index_sequence<1, 3, 5, 7>{} 는 1, 3, 5, 7을 템플릿 파라미터로 갖는 std::index_sequence 객체를 생성합니다.

std::make_index_sequence  std::index_sequence 의 type-alias 이므로, 그냥 객체 만드는거라 중괄호(Uniform Initialization)나 괄호(기본생성자) 중 뭘 써도 무방합니다.

While calling make_array(v, std::make_index_sequence<3>()) 

1. std::make_index_sequence<3>() creates a std::index_sequence<0, 1, 2> object, and pass it to make_array function.

2. the template parameter of std::index_sequence is deduced

3. unpacking vector elements to std::initializer_list

4. construct std::array with it(which size has to be known at compile-time).

5. and returns the std::array

  ※ sizeof...() operator returns the total number of the parameter pack.

 

Same things are happening in make_array(v, std::index_sequence<1, 3, 5, 7>{}).

 

Since you are passing an std::index_sequence object, so you can use either parentheses(calling a default constructor) or a uniform initializer {}.

 


예시 2

Example 2

 

std::tuple 객체에 인덱싱을 할 때도 요긴하게 사용됩니다.

std::tuple 객체 자체는 iterator 등을 제공하지 않기 때문에, 숫자로 모든 원소에 대해 접근(std::get)할 때 std::index_sequence 가 사용됩니다

It can also be used in accessing all members of tuple-like(std::pair, std::tuple) object, which doesn't provide its iterator.

template<typename T>
void print_all(const T& val){
  std::cout << val << std::endl;
}

template<typename T, typename ...Ts>
void print_all(const T& val, const Ts&... vals){
  std::cout << val << " ";
  print_all(vals...);
}

template<typename ...Args, std::size_t... I>
void print_tuple_imp(const std::tuple<Args...>& tup, std::index_sequence<I...>){
  print_all(std::get<I>(tup)...);
}

template<typename ...Args>
void print_tuple(const std::tuple<Args...>& tup){
  print_tuple_imp(tup, std::index_sequence_for<Args...>());
}

int main()
{
  print_tuple(std::make_tuple(1, 2, 3.14));
  
  return 0;
}

 

 

출력은

Outputs

1 2 3.14

 

C++ 20 이전에는 std::cout 이 parameter unpacking 을 지원하지 않기 때문에.. print_all 이라는 함수를 하나 더 만들었습니다.

print_tuple 함수에서 0~2까지의 템플릿 파라미터를 가지는 std::index_sequence 를 만들고, 

print_tuple_imp 함수에서 모든 std::tuple 객체의 원소를 unpack 해서 print_all 함수에 전달해 주는 모습입니다.

 

Since std::cout doesn't support parameter unpacking in below C++ 20, print_all function is used to print parameter packs.

print_tuple makes std::index_sequence<0, 1, 2> and pass it to print_tuple_imp.

print_tuple_imp is unpacking all tuple element and passing it to print_all.

 


 

 

순차적인 정수 시퀸스가 아닌, 임의의 정수 시퀸스를 아래처럼 간단하게 만들 수 있습니다.

You can make a custom integer sequence like the below.

 

template <std::size_t ... Is>
constexpr auto zero_sequence (std::index_sequence<Is...> const &)
-> decltype( std::index_sequence<0*Is...>{} ); // multiplying 0 to all template parameters

template <std::size_t N>
using make_zero_sequence
= decltype(zero_sequence(std::make_index_sequence<N>{}));

template<typename T>
void print_container(const T& arr){
  for(const auto elem : arr)
    std::cout << elem << " ";
  std::cout << std::endl;
}

int main()
{
  std::vector<int> v = {1, 2, 3, 4, 5, -1, -2, -3, -4, -5};
  print_container(make_array(v, vmake_zero_sequence<10>()));
  
  return 0;
}

 

출력은,

Outputs

1 1 1 1 1 1 1 1 1 1 

 

질문, 피드백, 댓글은 글쓴이에게 큰 힘이 됩니다!

Questions, Feedbacks, and comments are very helpful to me!