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