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_sequencesize_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_sequencestd::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 << ",";
    });
}
posted @ 2025-07-06 12:26  3的4次方  阅读(46)  评论(0)    收藏  举报