C++14之std::index_sequence和std::make_index_sequence

本文详细介绍了C++中的std::integer_sequence、std::index_sequence和std::make_index_sequence在模板元编程中的应用,包括它们的定义、用途以及如何在编译时生成和操作序列,特别展示了如何用于打印序列值、编译时计算和std::tuple元素的访问。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

相关文章系列

std::apply源码分析

C++之std::tuple(一) : 使用精讲(全)

目录

1.std::integer_sequence

2.std::index_sequence

3.std::make_index_sequence

4.运用

4.1.打印序列的值

4.2.编译时求值

4.3.std::tuple访问值

5.总结


1.std::integer_sequence

        运行时定义一个随机序列比较简单,那么编译时表示一个随机序列怎么办呢?于是std::interger_sequence产生了,它的作用就是表示一个integral数据的序列,这里的integral数据代表std::is_integral为true的数据类型,它主要包括下图列举的一些数据:

std::interger_sequence的定义如下:

template <class _Ty, _Ty... _Vals>
struct integer_sequence { // sequence of integer parameters
    static_assert(is_integral_v<_Ty>, "integer_sequence<T, I...> requires T to be an integral type.");

    using value_type = _Ty;

    _NODISCARD static constexpr size_t size() noexcept {
        return sizeof...(_Vals);
    }
};

通过static_assert和std::is_integral_v限制数据的类型为integral,  通过constexpr编译时求序列的大小。

2.std::index_sequence

它定义如下:

template <size_t... _Vals>
using index_sequence = integer_sequence<size_t, _Vals...>;

它是一个类模板,表示数据类型为std::size_t的一个序列。它在模板元编程中用于编译时迭代。

3.std::make_index_sequence

它的定义如下:

template <class _Ty, _Ty _Size>
using make_integer_sequence = __make_integer_seq<integer_sequence, _Ty, _Size>;

template <size_t _Size>
using make_index_sequence = make_integer_sequence<size_t, _Size>;

template <class... _Types>
using index_sequence_for = make_index_sequence<sizeof...(_Types)>;

   从中可以看出std::make_index_sequence是一个模板别名,它生成一个std::index_sequence类型的对象,该对象包含一系列递增的整数。这个工具在编译时生成一系列的索引,常常用于元编程和编译时计算。

        这里,std::size_t  _size是一个模板参数,表示生成的序列的大小。std::make_integer_sequence是一个模板,它生成一个包含从0到N-1的整数序列的类型。std::index_sequence_for则是一个模板别名,它根据给定的类型生成一个std::index_sequence。

        在实际编程中,我们通常使用std::make_index_sequence来生成一个索引序列,然后使用这个序列来访问元组或数组的元素。这样,我们可以在编译时生成代码,而不需要在运行时进行循环。

        std::make_index_sequence的实现依赖于C++的模板元编程。它使用了递归模板和特化来生成一个包含递增整数的序列。

        以下是一个简化的std::make_index_sequence的实现:

template<std::size_t... Ints>
struct index_sequence {
};

template<std::size_t N, std::size_t... Ints>
struct make_index_sequence_helper : make_index_sequence_helper<N - 1, N - 1, Ints...> {
};

template<std::size_t... Ints>
struct make_index_sequence_helper<0, Ints...> {
    using type = index_sequence<Ints...>;
};

template<std::size_t N>
using make_index_sequence = typename make_index_sequence_helper<N>::type;

        在这个实现中,make_index_sequence_helper是一个模板,它递归地生成一个包含递增整数的序列。当N为0时,递归结束,生成的序列被包装在index_sequence中。

4.运用

4.1.打印序列的值

代码如下所示:

#include <array>
#include <iostream>
#include <tuple>
#include <utility>

template <typename T, T... ints>
void print_sequence(std::integer_sequence<T, ints...> int_seq) {
  std::cout << "The sequence of size " << int_seq.size() << ": ";
  ((std::cout << ints << ' '), ...);
  std::cout << '\n';
}

// 转换数组为 tuple
template <typename Array, std::size_t... I>
auto a2t_impl(const Array &a, std::index_sequence<I...>) {
  return std::make_tuple(a[I]...);
}

template <typename T, std::size_t N, typename Indices = std::make_index_sequence<N>>
auto a2t(const std::array<T, N> &a) {
  return a2t_impl(a, Indices{});
}

// 漂亮地打印 tuple
template <class Ch, class Tr, class Tuple, std::size_t... Is>
void print_tuple_impl(std::basic_ostream<Ch, Tr> &os, const Tuple &t, std::index_sequence<Is...>) {
  ((os << (Is == 0 ? "" : ", ") << std::get<Is>(t)), ...);
}

template <class Ch, class Tr, class... Args>
auto &operator<<(std::basic_ostream<Ch, Tr> &os, const std::tuple<Args...> &t) {
  os << "(";
  print_tuple_impl(os, t, std::index_sequence_for<Args...>{});
  return os << ")";
}

int main() {
  print_sequence(std::integer_sequence<unsigned, 9, 2, 5, 1, 9, 1, 6>{});
  print_sequence(std::make_integer_sequence<int, 20>{});
  print_sequence(std::make_index_sequence<10>{});
  print_sequence(std::index_sequence_for<float, std::iostream, char>{});

  std::array<int, 4> array = {1, 2, 3, 4};

  // 转换 array 为 tuple
  auto tuple = a2t(array);
  static_assert(std::is_same<decltype(tuple), std::tuple<int, int, int, int>>::value, "");

  // 打印到 cout
  std::cout << tuple << '\n';
}

输出:

The sequence of size 7: 9 2 5 1 9 1 6
The sequence of size 20: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
The sequence of size 10: 0 1 2 3 4 5 6 7 8 9
The sequence of size 3: 0 1 2
(1, 2, 3, 4)

在上面的例子中,我们定义了一个 print_sequence函数,它接受一个序列作为参数并打印其内容。我们通过std::make_index_sequence用不同的方法产生了多个序列并都能正确打印其值。

4.2.编译时求值

现在假如我们需编译期的一组1到4的平方值。你会怎么办呢?思考一下,可以这些写:

constexpr static size_t const_nums[] = {0, 1, 4, 9, 16};

int main() {
    static_assert(const_nums[3] == 9); 
}

这个代码肯定是正确的,但是如果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...>) {
    constexpr auto nums = std::array{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>{});
}

int main() {
    static_assert(const_nums<101>(100) == 100 * 100); 
    return 0;
}

        首先我们定义了一个constexpr的静态函数const_nums。它通过我们本文的主角std::make_index_sequence来构造了一组0,1,2,3 .... N - 1的一组编译器的可变长度的整数列。(注意,这里调用std::make_index_sequence{}的构造函数没有任何意义,纯粹只是利用了它能够生成编译期整数列的能力。)

        接着我们来看squere_num函数,这就是我们实际进行平方计算,并生成编译期静态数组的地方了,它的实现很简单,就是依次展开通过std::make_index_sequence生成的数字,并进行平方计算,最后塞到std::array的构造函数之中进行构造。

std::make_index_sequence 本身不执行任何操作;它只是生成一个整数序列。实际的遍历和操作通常在一个辅助函数中完成,该函数接受整数序列作为参数。

4.3.std::tuple访问值

        之前在 C++之std::tuple(一) : 使用精讲(全) 中讲解了用 递归遍历元素和 std::apply 方式来访问std::tuple的元素值,这里我们来讲一下用std::index_sequence和std::make_index_sequence来访问std::tuple的元素值。示例代码如下:

template <typename Tuple, typename Func, size_t ... N>
void func_call_tuple(const Tuple& t, Func&& func, std::index_sequence<N...>) {
    static_cast<void>(std::initializer_list<int>{(func(std::get<N>(t)), 0)...});
}

template <typename ... Args, typename Func>
void visitTuple(const std::tuple<Args...>& t, Func&& func) {
    func_call_tuple(t, std::forward<Func>(func), std::make_index_sequence<sizeof...(Args)>{});
}

int main() {
    auto t = std::make_tuple(155, false, 566.90, "hello world!");
    visitTuple(t, [](auto&& item) {
        std::cout << item << ",";
    });
}

        这个代码首先定义了一个travel_tuple的函数,并且利用了std::make_index_sequence将tuple类型的参数个数进行了展开,生成了0到N - 1的编译期数字。

        接下来我们再利用func_call_tuple函数和展开的编译期数字,依次调用std::get<N>(tuple),并且通过lambda表达式依次的调用,完成了遍历tuple的逻辑。

在C++17中,std::apply也利用std::make_index_sequence实现了std::apply,详情请查看std::apply源码分析_{ return std::forward<_fn>(__f)(std::forward<_args-CSDN博客

5.总结

   std::index_sequence和std::make_index_sequence结合使用这种方法是模板元编程中的一个常见技巧,它允许我们在编译时生成和执行复杂的操作,而不需要在运行时付出任何额外的性能开销。

参考:

https://zh.cppreference.com/w/cpp/utility/integer_sequence

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值