我原先并不知道std::integer_sequence有什么用,直到我的膝盖中了一箭。
元组(Tuple)
元组是一种长度固定的允许有不同类型元素的集合,根据元素的个数不同又分别称作一元组、二元组、三元组等。C++11中标准库增加了一个叫std::tuple的类模板,用于表示元组。下面的代码演示了使用C++创建一个三元组。
1 2 3 4 5 6 7 8
| auto tuple = std::make_tuple(1, 'A', "破晓的博客"); std::cout << std::get<0>(tuple) << std::endl; std::cout << std::get<1>(tuple) << std::endl; std::cout << std::get<2>(tuple) << std::endl;
std::cout << std::get<int>(tuple) << std::endl; std::cout << std::get<char>(tuple) << std::endl; std::cout << std::get<const char*>(tuple) << std::endl;
|
输出
许多编程语言如C#、Python等也有tuple的概念。下面的代码演示了使用Python创建一个三元组。
1 2 3 4
| t = (1, 'A', "破晓的博客") print(t[0]) print(t[1]) print(t[2])
|
输出
Python从语言级别上支持将tuple展开为函数的参数,在Python中假设定义有这样一个函数func和一个元组t,下面的代码演示了将元组t的每个元素作为func函数的参数。
1 2 3 4
| def func(arg1, arg2, arg3): print(arg1, arg2, arg3) t = (1, 'A', "破晓的博客") func(*t)
|
输出
可变参数模板(Variadic Template)
C++没有并没有对类似Python的这种特性提供语言或库的支持,本文的目的就是为了介绍如何在C++中实现将tuple展开作为函数的参数。假设有函数func和元组tuple如下:
1 2 3 4 5
| void func(int arg1, char arg2, const std::string& arg3); { } auto tuple = std::make_tuple(1, 'A', "破晓的博客");
|
手动将tuple中的元素逐个取出后作为参数调用应是下面这样的。
1
| func(std::get<0>(tuple), std::get<1>(tuple), std::get<2>(tuple));
|
观察手动调的参数,可以看出对于N元组,调用函数时的参数是这样的一个列表。
std::get<0>(t), std::get<1>(t), ……, std::get<N – 1>(t)
于是可以使用C++11的可变参数模板(Variadic Template)写一个这样的函数模板apply
1 2 3 4 5
| template<std::size_t... I, typename F, typename T> void apply(F f, const T& t) { func(std::get<I>(t)...); }
|
其中第1行中的std::size_t… I称为模板参数组(Template Parameter Pack),第4行的std::get<I>(t)..称为参数组展开(Parameter Pack Expansion)。
使用这个函数模板apply将tuple展开作为参数调用func函数是这样写的。
1
| apply<0, 1, 2>(func, tuple);
|
显然这样的调用方式还不够优雅,因为需要手动写模板参数。对于N元组,这里的模板参数是这样一个序列。
0, 1, 2, 3, 4, …, N-1
如果能够使用模板参数推导(Template argument deduction)自动推导出这个序列就方便多了。C++14中的std::integer_sequence提供了这种机制。
std::integer_sequence
C++14中标准库增加了std::integer_sequence类模板用于表示编译期的整数序列。其声明如下
1 2
| template<class T, T... Ints> class integer_sequence;
|
下面是各模板参数的描述
模板参数 |
描述 |
T |
整数序列元素的类型 |
…Ints |
整数序列的参数组(非类型) |
为了方便使用,C++14的标准库中还使用C++11的模板别名(Template Typedef或Template Alias)特性声明了下面这几个辅助使用的别名模板。
1 2 3 4 5 6 7 8 9 10 11
| template<std::size_t... Ints> using index_sequence = std::integer_sequence<std::size_t, Ints...>;
template<class T, T N> using make_integer_sequence = std::integer_sequence<T, >;
template<std::size_t N> using make_index_sequence = make_integer_sequence<std::size_t, N>;
template<class... T> using index_sequence_for = std::make_index_sequence<sizeof...(T)>;
|
下面的代码演示了使用std::integer_sequence创建一个含有元素0, 1, 2, 3, …, 9的vector。通过第13行的模板参数10,推导出第5行的模板参数组为0, 1, 2, 3, …, 9。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <utility> #include <vector> #include <iostream>
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; }
|
输出
使用std::integer_sequence实现apply函数模板
对于tuple可以使用std::tuple_size获取元组的元素个数,类似于前面创建vector使用std::integer_sequance实现apply函数模板的代码如下。
1 2 3 4 5 6 7 8 9 10 11
| template<typename F, typename T, std::size_t... I> void apply_impl(F f, const T& t, std::index_sequence<I...>) { f(std::get<I>(t)...); }
template<typename F, typename T> void apply(F f, const T& t) { apply_impl(f, t, std::make_index_sequence<std::tuple_size<T>::value>()); }
|
至此,使用apply函数模板时就不再需要手动写模板参数了。
缺点是如果func函数有返回值,其返回值会被忽略。对于返回值的问题,可以使用C++11的新的函数声明语法(New Function Declarator Syntax)特性来解决。
1 2 3 4 5 6 7 8 9 10 11
| template<typename F, typename T, std::size_t... I> auto apply_impl(F f, const T& t, std::index_sequence<I...>) -> decltype(f(std::get<I>(t)...)) { return f(std::get<I>(t)...); }
template<typename F, typename T> auto apply(F f, const T& t) -> decltype(apply_impl(f, t, std::make_index_sequence<std::tuple_size<T>::value>())) { return apply_impl(f, t, std::make_index_sequence<std::tuple_size<T>::value>()); }
|
最后附上apply函数模板的完整实现和使用演示的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include <tuple> #include <iostream> #include <string> #include <utility>
int func1(int arg1, char arg2, double arg3, const std::string& arg4) { std::cout << "call func1(" << arg1 << ", " << arg2 << ", " << arg3 << ", " << arg4 << ")" << std::endl; return 0; }
int func2(int arg1, int arg2) { std::cout << "call func2(" << arg1 << ", " << arg2 << ")" << std::endl; return arg1 + arg2; }
template<typename F, typename T, std::size_t... I> auto apply_impl(F f, const T& t, std::index_sequence<I...>) -> decltype(f(std::get<I>(t)...)) { return f(std::get<I>(t)...); }
template<typename F, typename T> auto apply(F f, const T& t) -> decltype(apply_impl(f, t, std::make_index_sequence<std::tuple_size<T>::value>())) { return apply_impl(f, t, std::make_index_sequence<std::tuple_size<T>::value>()); }
int main() { using namespace std::literals::string_literals; auto tuple1 = std::make_tuple(1, 'A', 1.2, "破晓的博客"s); auto result1 = apply(func1, tuple1); std::cout << "result1 = " << result1 << std::endl;
auto tuple2 = std::make_tuple(1, 2); auto result2 = apply(func2, tuple2); std::cout << "result2 = " << result2 << std::endl; }
|
输出
1 2 3 4
| call func1(1, A, 1.2, 破晓的博客) result1 = 0 call func2(1, 2) result2 = 3
|