C++14 std::index_sequence
C++14 std::index_sequence
介绍
C++14在标准库里添加了一个很有意思的元函数: std::integer_sequence。并且通过它衍生出了一系列的帮助模板:
std::make_integer_sequence, std::make_index_sequence, std:: index_sequence_for。它可以帮助我们完成在编译期间获取了一组编译期整数序列的工作。
注意:这组模板都是空类,实际上是由编译器黑魔法实现的。
std::integer_sequence
表示一个integral数据的序列,这里的integral数据代表 std::is_integral 为true的数据类型。
template< class T, T... Ints > // T是整数类型,Ints是产生的T类型的编译期整数序列,通过形参包展开使用
class integer_sequence;
有一个成员函数,size() 用于获取编译期整数序列的长度(就是 sizeof...(Ints) )。
编译器生成的编译期整数序列就是这里的形参包 Ints ,所以这个形参包是类似“传出参数”的东西。
std::index_sequence
就是 std::integer_sequence 在 size_t 特化下的别名,常用于构造一个编译期序列,然后用形参包展开来实现编译期迭代。
template< std::size_t... Ints >
using index_sequence = std::integer_sequence<std::size_t, Ints...>;
构造函数
这里就是构造了一组数字序列0,1,2,3...N - 1的一组std::index_sequence。然后就可以利用这组序列的数字做任何我们想做的事情了。
注意这些构造函数没啥意义,只是为了让编译器在这里认为有一个编译期序列,即 std::integer_sequence/std::index_sequence ,仅此而已。
template< class T, T N >
using make_integer_sequence = std::integer_sequence<T, /* a sequence 0, 1, 2, ..., N-1 */>;
template< std::size_t N >
using make_index_sequence = std::make_integer_sequence<std::size_t, N>;
template< class... T >
using index_sequence_for = std::make_index_sequence<sizeof...(T)>;
使用
例子1
在C++之中有一个很常见的需求,定义一组编译期间的数组作为常量,并在运行时或者编译时利用这些常量进行计算。
现在假如我们需编译期的一组1到4的平方值。你会怎么办呢?可以这些写:
constexpr static size_t const_nums[] = {0, 1, 4, 9, 16};
int main() {
static_assert(const_nums[3] == 9);
}
Bingo, 这个代码肯定是正确的,但是如果4扩展到了20或者100?怎么办呢?我们可以用std::make_index_sequence和std::index_sequence来帮助我们实现这个逻辑:
template <size_t ...N>
static constexpr auto square_nums(size_t index, std::index_sequence<N...>) { // 注意这里需要用形参包展开,因为std::index_sequence的Ints模板形参是一个形参包。且不需要用变量记录这个序列
constexpr auto nums = std::array<size_t, sizeof...(N)>{(N * N) ...};
return nums[index];
}
template <size_t N>
constexpr static auto const_nums(size_t index) {
return square_nums(index, std::make_index_sequence<N>{}); // 这里的make_index_sequence<N>{}只是为了生成0~N-1的序列,用于传入第2行的第二个参数。
}
int main() {
static_assert(const_nums<101>(100) == 100 * 100);
}
这代码咋看之下有些吓人,不过没关系,我们来一点点的庖丁解牛的解释清楚它是如何工作的。
首先我们定义了一个constexpr的静态函数const_nums。它通过我们本文的主角std::make_index_sequence来构造了一组0,1,2,3 .... N - 1的一组编译器的可变长度的整数列。(注意,这里调用std::make_index_sequence{}的构造函数没有任何意义,纯粹只是利用了它能够生成编译期整数列的能力)
然后再利用形参包展开得到一个编译期的 std::array 。
例子2
如何遍历一个 std::tuple。(不能使用C++17的std::apply)
这个时候就要再次请出我们今天的主角,使用std::make_index_sequnce来完成这个工作了。我们来看下面这部分代码:
template<typename ...Args, size_t ...I, typename F>
void func_call_tuple(const std::tuple<Args...>& t, std::index_sequence<I...>, F&& f) {
// 如果是C++17,可以直接用折叠表达式,而无需搞一个数组来让形参包展开:(f(std::get<I>(t)), ...);
(void)std::array<int, sizeof...(I)>{ (f(std::get<I>(t)), 0)... };
}
template<typename ...Args, typename F>
void travel_tuple(const std::tuple<Args...>& t, F&& f) {
func_call_tuple(t, std::make_index_sequence<sizeof...(Args)>{}, std::forward<F>(f));
}
int main() {
auto t = std::make_tuple(1, 4.56, "happen lee");
travel_tuple(t, [](auto&& item) {
std::cout << item << ",";
});
}

浙公网安备 33010602011771号