模板展开及integer_sequence妙用

本文详细介绍了如何自定义实现C++14的integer_sequence,并通过实例展示了其在常量调用、非常量调用以及std::tuple_size调用中的应用。此外,还探讨了如何扩展integer_sequence以处理std::vector内容作为函数参数的问题,分析了利用integer_sequence在编译时动态展开参数的原理,并给出了完整实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

integer_sequence自实现版本

#include <iostream>
  // the type holding sequences
  template <int... Ns> 
  struct sequence {}; 
  
  // First define the template signature
  template <int... Ns> 
  struct seq_gen;
  
  // forward extend recursively
  template <int I, int... Ns> 
  struct seq_gen<I, Ns...> {
      // Take front most number of sequence,
      // decrement it, and prepend it twice.
      // First I - 1 goes into the counter,
      // Second I - 1 goes into the sequence.
      using type = typename seq_gen<I - 1, I - 1, Ns...>::type;
  };
  
  // Recursion abort
  template <int... Ns> 
  struct seq_gen<0, Ns...> {
      using type = sequence<Ns...>;
  };
  
  template <int N>
  using sequence_t = typename seq_gen<N>::type;
  
  template <int... Is> 
  void show(sequence<Is...>) {
     (((std::cout << " ") << Is), ...);                                             
  }
  
  int main() {
      show(sequence_t<10>());
  }

integer_sequence.cpp
g++ integer_sequence.cpp -o integer_sequence --std=c++17

analyze

sequence_t<10>是seq_gen<10>::type 这个类型的别名.
seq_gen<10>唯一能匹配的模板特化为:seq_gen<10, int…Ns> 。现在这个Ns是空列表{}。
这是1个模板参数变成2组模板参数质的飞跃。
我们用伪代码seq_gen<10, {}>表示seq_gen<10, {}>::type 等价于seq_gen<10-1,10-1, {}>::type即seq_gen<9,9, {}>::type
seq_gen<9,9, {}>唯一能匹配的模板特化为:seq_gen<9, int…Ns>。现在这个Ns是列表{9}。
我们用伪代码seq_gen<9, {9}>表示.
seq_gen<9, {9}>::type 等价于seq_gen<9-1,9-1, {9}>::type即seq_gen<8,8, {9}>::type
seq_gen<8,8, {9}>唯一能匹配的模板特化为:seq_gen<8, int…Ns>。现在这个Ns是列表{8,9}。
我们用伪代码seq_gen<8, {8,9}>表示.

seq_gen<0, {0,1,2,3,4,5,6,7,8,9}>唯一能匹配的模板特化为第22行,递归终结特化。
seq_gen<0, int…Ns>::type等价于sequence<0,1,2,3,4,5,6,7,8,9>.

improvement refer to [https://stackoverflow.com/questions/17424477/implementation-c14-make-integer-sequence]

C++14

  #include <iostream>  
  #include <utility>
  #include <vector>
  
  template <std::size_t... I>
  std::vector<std::size_t> make_index_vector(std::index_sequence<I...>) {
      return {I...};
  }

常量调用

  int main() {
      auto vec = make_index_vector(std::make_index_sequence<10>());
      for (auto i : vec) {
          std::cout << i << ' ';
      }   
      std::cout << std::endl;
  }    

编译 g++ integer_sequence.cpp -o integer_sequence --std=c++14
./integer_sequence

0 1 2 3 4 5 6 7 8 9 

非常量调用

  int main() {
      std::vector<int> x = {1, 2, 3}; 
✗     auto vec = make_index_vector(std::make_index_sequence<x.size()>());
      for (auto i : vec) {
          std::cout << i << ' ';
      }   
      std::cout << std::endl;
  }    

编译 g++ integer_sequence.cpp -o integer_sequence --std=c++14

integer_sequence.cpp:12:59: error: non-type template argument is not a constant expression
    auto vec = make_index_vector(std::make_index_sequence<x.size()>());
                                                          ^~~~~~~~
integer_sequence.cpp:12:61: note: non-constexpr function 'size' cannot be used in a constant expression
    auto vec = make_index_vector(std::make_index_sequence<x.size()>());
                                                            ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/vector:632:15: note: declared here
    size_type size() const _NOEXCEPT
              ^
1 error generated.

tuple_size调用

std::array, std::tuple需要在编译的时候确定大小,所以可以通过std::tuple_size来获取编译时的常量表达式。可以在调用std::make_index_sequence中使用。

int main() {                                                                         
    using array_type = std::array<int, 10>;
    auto vec = make_index_vector(std::make_index_sequence<std::tuple_size<array_type>::value>());      for (auto i : vec) { 
        std::cout << i << ' ';
    } 
    std::cout << std::endl;
} 

扩展

问题

那么如果希望能够扩展std::vector的内容作为一个函数的实参输入该如何解决呢?
例如, 有如下的函数add, 该函数计算的内容保存在std::vector,或者std::tuple

template<typename R, typename ... Args>
R add(Args... args) {
}

及我们希望能够通过类似如下的方式调用

std::vector<int> vec = {1,2,3};
add(vec)

分析

该问题的最基本的问题是如何动态的将vec展开为vec[0]…vec[N-1]的格式,并调用对应的函数
利用上面提到的支持,integer_sequence可以在编译时产生一个序列,而实际上该序列对一个对模板的类型特化序列如sequence<0,1,2,3,4,5,6,7,8,9>是对sequence模板的一个特化。而这种类型的特化,可以在函数调用的过程中动态决定参数类型及数量(编译期展开)。
如传递sequence<0,1,2,3,4,5,6,7,8,9>类型到如下的函数,此时,函数的参数为了匹配传入的类型,需要对模板进行推演: 此时int…的整数序列I需要展开以匹配sequence<0,1,2,3,4,5,6,7,8,9>。那么编译器就会赋予I为如下的整数序列“0,1,2,3,4,5,6,7,8,9”。
以上的过程核心是通过参数的类型匹配以决定模板可变参数的确切类型,并进一步在函数体中利用可变参数的展开规则“动态的展开”可变参数,以上均为编译期的行为。

template<int... I>
void add(sequence<I...>) {
	std::vector<int> vec;
	vec.push_back(I...);
}

实现

根据以上的分析,我们可以通过编译期“动态”展开integer_sequence,以此作为容器的索引,进一步展开容器内容,完整的实现如下

#include <array>
#include <iostream>
#include <utility>
#include <vector>
using namespace std;

template <std::size_t... I>
std::vector<std::size_t> make_index_vector(std::index_sequence<I...>) {
    return {I...};
}

/*
 * apply underline implementation by invoke "func(c[0]...c[N-1] )
 * typename R         : decalre type of return value for the function signature
 * typename... Args   : decalre type of parameters for the function signature
 * typename Container : decalre type of parameters for the function input values
 *                      (could be any type that operator[] supported, such as  
 *                      std::vector)
 * size_t... I        : indexing list of the contianer[0 ... N-1]
 */
template <typename R, typename... Args, typename Container, size_t... I>
R apply_impl(R (*func)(Args...), const Container& c, std::index_sequence<I...>) {
    return (*func)(c[I]...);
}

/*
 * invoke "func"(including return value) with container "c" as parameter
 * typename R         : decalre type of return value for the function signature
 * typename... Args   : decalre type of parameters for the function signature
 * typename Container : decalre type of parameters for the function input values
 *                      (could be any type that operator[] supported, such as 
 *                      std::vector)
 */
template <typename R, typename... Args, typename Container>
R apply(R (*func)(Args...), const Container& c) {
    printf("%s %lu\n", __func__, sizeof...(Args));
    return apply_impl(func, c, std::make_index_sequence<sizeof...(Args)>());
}

/*
 * invoke "func"(without return value) with container "c" as parameter
 * typename... Args   : decalre type of parameters for the function signature
 * typename Container : decalre type of parameters for the function input values
 *                      (could be any type that operator[] supported, such as 
 *                      std::vector)
 */
template <typename... Args, typename Container>
void apply(void (*func)(Args...), const Container& c) {
    printf("%s %lu\n", __func__, sizeof...(Args));
    apply_impl(func, c, std::make_index_sequence<sizeof...(Args)>());
}

struct context {
    static int add(int a, int b) {
        return a + b;
    }
};

void add_void(int a, int b) {
    printf("void-add %d + %d = %d\n", a, b, a + b);
}

int add(int a, int b) {
    printf("int-add %d + %d = %d\n", a, b, a + b);
    return a + b;
}

int main() {
    std::vector<int> vt = {1, 2};
    apply(context::add, vt);

    using array_type = std::array<int, 10>;
    auto vec = make_index_vector(std::make_index_sequence<std::tuple_size<array_type>::value>());
    for (auto i : vec) {
        std::cout << i << ' ';
    }
    std::cout << std::endl;
}

// g++ integer_sequence.cpp -o integer_sequence --std=c++14]
//

Reference

[0]. C++14使用std::integer_sequence展开tuple作为函数的参数:https://blog.poxiao.me/p/unpacking-a-tuple-to-call-a-function-in-cpp14/
[1]. Implementation C++14 make_integer_sequence:
https://stackoverflow.com/questions/17424477/implementation-c14-make-integer-sequence
[2]. C++ integer_sequence:
https://www.saowen.com/a/e999838f8eb5577676e152163c690609a6add9d28e6b609eac69b702862be4ca
[3]. std::integer_sequence:
https://zh.cppreference.com/w/cpp/utility/integer_sequence
[4]. C++11 标准库源代码剖析: https://www.jianshu.com/p/de58dafdc621

/usr/local/include/ceres/internal/integer_sequence_algorithm.h:135:59: note: expected a type, got ‘N’ /usr/local/include/ceres/internal/integer_sequence_algorithm.h:146:39: error: ‘integer_sequence’ is not a member of ‘std’ 146 | struct ExclusiveScanImpl<T, Sum, std::integer_sequence<T>, SeqOut> { | ^~~~~~~~~~~~~~~~ /usr/local/include/ceres/internal/integer_sequence_algorithm.h:146:39: error: ‘integer_sequence’ is not a member of ‘std’ /usr/local/include/ceres/internal/integer_sequence_algorithm.h:146:57: error: wrong number of template arguments (3, should be 4) 146 | struct ExclusiveScanImpl<T, Sum, std::integer_sequence<T>, SeqOut> { | ^ /usr/local/include/ceres/internal/integer_sequence_algorithm.h:130:8: note: provided for ‘template<class T, T Sum, class SeqIn, class SeqOut> struct ceres::internal::ExclusiveScanImpl’ 130 | struct ExclusiveScanImpl; | ^~~~~~~~~~~~~~~~~ /usr/local/include/ceres/internal/integer_sequence_algorithm.h:160:53: error: ‘integer_sequence’ is not a member of ‘std’ 160 | typename ExclusiveScanImpl<T, T(0), Seq, std::integer_sequence<T>>::Type; | ^~~~~~~~~~~~~~~~ /usr/local/include/ceres/internal/integer_sequence_algorithm.h:160:53: error: ‘integer_sequence’ is not a member of ‘std’ /usr/local/include/ceres/internal/integer_sequence_algorithm.h:160:70: error: template argument 4 is invalid 160 | typename ExclusiveScanImpl<T, T(0), Seq, std::integer_sequence<T>>::Type; | ^ /usr/local/include/ceres/internal/integer_sequence_algorithm.h:160:16: error: expected nested-name-specifier 160 | typename ExclusiveScanImpl<T, T(0), Seq, std::integer_sequence<T>>::Type; | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In file included from /usr
最新发布
03-25
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值