一文读懂 C++ 14 std::make_index_sequence

一文读懂 C++ 14 std::make_index_sequence

编码文章call10242024-12-18 12:30:1231A+A-

一、背景

C++14在标准库里添加了一个很有意思的元函数: std::integer_sequence。并且通过它衍生出了一系列的帮助模板:

  1. std::make_integer_sequence
  2. std::make_index_sequence
  3. std:: index_sequence_for

在新特性的加持下,它可以帮助我们在编译期间获取一组编译期整数的工作这其中重点得聊聊std::make_index_sequence 与 std::index_sequence。

二、std::make_index_sequence 介绍与使用

2.1 问题:如何在编译期间获取一组数字的平方?

现在有一个这样的需求:编译期间获取{1,2,3,4} 的平方数组即{1,4,9,16}?聪明的你可能会这样写:

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

Bingo, 这个代码肯定是正确的,但是如果4扩展到了n ,怎么办呢?犯难了吧。

莫急,下面有请本文的女主角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 ...}; //得在C++ 17 以上  运行
    //return nums[index];
    return nums;
}

template<size_t N>
constexpr static auto const_nums(size_t index) {
    return square_nums(index, std::make_index_sequence<N>{});
}

void test_make_index_sequence() {
    auto res = const_nums<11>(10);
    for (int i =0; i< res.size(); i ++) {
        printf(" %d 的 平方= %lu \n",i,res.at(i));
    }

}

int main() {
   test_make_index_sequence();
  return 0;
}

输出结果如下:

这两个模板函数是个啥?乍一看一脸蒙圈?不得不感叹 C++ 真**难!!

template<size_t ...N>
static constexpr auto square_nums(size_t index, std::index_sequence<N...>) {
    constexpr auto nums = std::array{N * N ...}; //得在C++ 17 以上  运行
    return nums;
}

template<size_t N>
constexpr static auto const_nums(size_t index) {
    return square_nums(index, std::make_index_sequence<N>{});
}

这两个模板函数是让人蒙圈的点为:std::index_sequencestd::make_index_sequence,要了解std::make_index_sequence是如何工作的,就得先看看它的基础类std::index_sequence。翻了参考手册发现std::index_sequence源码如下:

template<std::size_t... Ints>
using index_sequence = std::integer_sequence<std::size_t, Ints...>;

由上面的代码看,它很简单,就是一个int类型,加上一组int数字,其实原理就是生成一组T类型的编译期间数字序列。它本质上就是个空类,我们就是要获取这个编译期的数字序列。那是如何生成一个数字序列的呢?下面我们使用模板元编程来实现一个。

因c++14已定义index_sequence,为避免符号冲突,使用index_seq代替。

template<int ... N>
struct index_seq
{
};

/**
 * @brief 递归函数
 *
 * @tparam N
 * @tparam M
 */
template<int N, int ...M>
struct make_index_seq: public make_index_seq<N - 1, N - 1, M...>
{

};

/**
 * 偏特化实现  递归终止
 * @brief 递归终止条件
 * @tparam M
 */
template<int ...M>
struct make_index_seq<0, M...> : public index_seq<M...>
{

};

上述实现的递归函数如下:

seq(0) = 0

seq(N) = N-1 + seq(N-1) (N > 1)

实现的原理是,当给定一个整数N,如3,定义make_index_seq<3>() 对象时,模板可变参数M,由空逐渐推导为序列0,1,2。

即make_index_seq<3> 时,M 为空。

  1. make_index_seq<3-1,3-1, M...>时,M 为3-1 = 2
  2. make_index_seq<2-1,2-1,M...>时,M为2-1=1,2 即序列(1,2)
  3. make_index_seq<1-1,1-1,M...>时,M为1-1 = 0, 1,2即序列(0,1,2)
  4. make_index_seq<0,M...>时,M为(0,1,2)此时,make_index_seq<3>实际继承自index_seq<0,1,2>

这样就生成了编译期的整数序列。

测试:

#include <iostream>
#include <tuple>
using namespace std;

template<int ... N>
struct index_seq
{
};

/**
 * @brief 递归函数
 *
 * @tparam N
 * @tparam M
 */
template<int N, int ...M>
struct make_index_seq: public make_index_seq<N - 1, N - 1, M...>
{

};
/**
 * @brief 递归终止条件
 *
 * @tparam M
 */
template<int ...M>
struct make_index_seq<0, M...> : public index_seq<M...>
{

};

template<int ... N>
decltype(auto) fun(index_seq<N...> is)
{
    return make_tuple(N...);
}

int main()
{
    auto t = fun(make_index_seq<3>());
    cout << std::get<0>(t) << endl;
    cout << std::get<1>(t) << endl;
    cout << std::get<2>(t) << endl;
    return 0;
}
// g++ --std=c++14 test.cpp -Wall -pedantic

通过上述的介绍。想必大家应该了解了std::make_index_sequence的实现原理了。接下来将介绍它最为重要的使用场景与std::tuple的结合。

2.2 问题:如何遍历一个std::tuple

这个时候就要再次请出我们今天的主角,使用std::make_index_sequnce和lambda表达式来完成这个工作了。我们来看下面这部分代码:

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 travel_tuple(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(1, 4.56, "happen lee");
    travel_tuple(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的逻辑。
点击这里复制本文地址 以上内容由文彬编程网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

文彬编程网 © All Rights Reserved.  蜀ICP备2024111239号-4