百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

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

ztj100 2025-01-14 19:12 55 浏览 0 评论

一、背景

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}?聪明的你可能会这样写:

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

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

莫急,下面有请本文的女主角std::index_sequence 上场。实现的代码如下:

Bash
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的逻辑。

相关推荐

30天学会Python编程:16. Python常用标准库使用教程

16.1collections模块16.1.1高级数据结构16.1.2示例...

强烈推荐!Python 这个宝藏库 re 正则匹配

Python的re模块(RegularExpression正则表达式)提供各种正则表达式的匹配操作。...

Python爬虫中正则表达式的用法,只讲如何应用,不讲原理

Python爬虫:正则的用法(非原理)。大家好,这节课给大家讲正则的实际用法,不讲原理,通俗易懂的讲如何用正则抓取内容。·导入re库,这里是需要从html这段字符串中提取出中间的那几个文字。实例一个对...

Python数据分析实战-正则提取文本的URL网址和邮箱(源码和效果)

实现功能:Python数据分析实战-利用正则表达式提取文本中的URL网址和邮箱...

python爬虫教程之爬取当当网 Top 500 本五星好评书籍

我们使用requests和re来写一个爬虫作为一个爱看书的你(说的跟真的似的)怎么能发现好书呢?所以我们爬取当当网的前500本好五星评书籍怎么样?ok接下来就是学习python的正确姿...

深入理解re模块:Python中的正则表达式神器解析

在Python中,"re"是一个强大的模块,用于处理正则表达式(regularexpressions)。正则表达式是一种强大的文本模式匹配工具,用于在字符串中查找、替换或提取特定模式...

如何使用正则表达式和 Python 匹配不以模式开头的字符串

需要在Python中使用正则表达式来匹配不以给定模式开头的字符串吗?如果是这样,你可以使用下面的语法来查找所有的字符串,除了那些不以https开始的字符串。r"^(?!https).*&...

先Mark后用!8分钟读懂 Python 性能优化

从本文总结了Python开发时,遇到的性能优化问题的定位和解决。概述:性能优化的原则——优化需要优化的部分。性能优化的一般步骤:首先,让你的程序跑起来结果一切正常。然后,运行这个结果正常的代码,看看它...

Python“三步”即可爬取,毋庸置疑

声明:本实例仅供学习,切忌遵守robots协议,请不要使用多线程等方式频繁访问网站。#第一步导入模块importreimportrequests#第二步获取你想爬取的网页地址,发送请求,获取网页内...

简单学Python——re库(正则表达式)2(split、findall、和sub)

1、split():分割字符串,返回列表语法:re.split('分隔符','目标字符串')例如:importrere.split(',','...

Lavazza拉瓦萨再度牵手上海大师赛

阅读此文前,麻烦您点击一下“关注”,方便您进行讨论和分享。Lavazza拉瓦萨再度牵手上海大师赛标题:2024上海大师赛:网球与咖啡的浪漫邂逅在2024年的上海劳力士大师赛上,拉瓦萨咖啡再次成为官...

ArkUI-X构建Android平台AAR及使用

本教程主要讲述如何利用ArkUI-XSDK完成AndroidAAR开发,实现基于ArkTS的声明式开发范式在android平台显示。包括:1.跨平台Library工程开发介绍...

Deepseek写歌详细教程(怎样用deepseek写歌功能)

以下为结合DeepSeek及相关工具实现AI写歌的详细教程,涵盖作词、作曲、演唱全流程:一、核心流程三步法1.AI生成歌词-打开DeepSeek(网页/APP/API),使用结构化提示词生成歌词:...

“AI说唱解说影视”走红,“零基础入行”靠谱吗?本报记者实测

“手里翻找冻鱼,精心的布局;老漠却不言语,脸上带笑意……”《狂飙》剧情被写成歌词,再配上“科目三”背景音乐的演唱,这段1分钟30秒的视频受到了无数网友的点赞。最近一段时间随着AI技术的发展,说唱解说影...

AI音乐制作神器揭秘!3款工具让你秒变高手

在音乐创作的领域里,每个人都有一颗想要成为大师的心。但是面对复杂的乐理知识和繁复的制作过程,许多人的热情被一点点消磨。...

取消回复欢迎 发表评论: