模板展開及integer_sequence妙用

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章