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

news2024/12/23 20:44:47

相关文章系列

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1509944.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OSI七层模型TCP四层模型横向对比

OSI 理论模型&#xff08;Open Systems Interconnection Model&#xff09;和TCP/IP模型 七层每一层对应英文 应用层&#xff08;Application Layer&#xff09; 表示层&#xff08;Presentation Layer&#xff09; 会话层&#xff08;Session Layer&#xff09; 传输层&#x…

【JavaScript】面试手撕深拷贝

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 深拷贝的作用深浅拷贝的区别浅拷贝深拷贝 深拷贝实现方式JSON.parse(JSON.stringi…

开发知识点-python-Tornado框架

介绍 Tornado是一个基于Python语言的高性能Web框架和异步网络库&#xff0c;它专注于提供快速、可扩展和易于使用的网络服务。由于其出色的性能和灵活的设计&#xff0c;Tornado被广泛用于构建高性能的Web应用程序、实时Web服务、长连接的实时通信以及网络爬虫等领域。 Torna…

【物联网设备端开发】FastBee Arduino固件开发指南

目录 一、收集数据 二、打开FastBeeArduino 源码 三、修改 Config.cpp 文件 四、修改物模型数据 五、小程序配网 本文以 WeMOS D1 R1&#xff08;8266WIFI 模块&#xff09;固件开发为例&#xff0c;实现以下功能&#xff1a; 设备认证设备 Mqtt 交互Wifi 类设备配网 一…

ffmpeg解码和渲染理解

ffmpeg解码和渲染理解 ffmpeg视频解码步骤 FFmpeg 是一个功能强大的跨平台多媒体处理工具&#xff0c;包含了音视频编解码、封装/解封装、过滤器等功能。下面是一般情况下使用 FFmpeg 进行视频解码的步骤&#xff1a; 初始化 FFmpeg 库&#xff1a;首先需要初始化 FFmpeg 库&a…

SAM(Segment Anything Model)大模型使用--point prompt

概述 本系列将做一个专题&#xff0c;主要关于介绍如何在代码上运行并使用SAM模型以及如何用自己的数据集微调SAM模型&#xff0c;也是本人的毕设内容&#xff0c;这是一个持续更新系列&#xff0c;欢迎大家关注~ SAM&#xff08;Segment Anything Model&#xff09; SAM基于…

自然语言处理的概念及发展介绍

自然语言处理&#xff08;Natural Language Processing&#xff0c;NLP&#xff09;是计算机科学、人工智能和语言学的交叉领域&#xff0c;旨在使计算机能够理解、解释和生成人类语言。自然语言处理的发展对于实现人机交互、信息检索、机器翻译、情感分析等应用至关重要。 概念…

【Java设计模式】十五、命令模式

文章目录 1、命令模式2、案例3、总结 1、命令模式 餐厅点餐&#xff1a; 创建一个厨师对象&#xff0c;让服务员对象调用厨师对象中的方法进行点餐通知&#xff0c;当后面厨师换人&#xff0c;服务员类的代码也要修改&#xff0c;耦合 不符合开闭。理想状态&#xff1a;服务员…

JVM 垃圾回收相关

一、什么是垃圾 目录 一、什么是垃圾回收 二、 死亡对象的判断算法 a) 引用计数算法 b)可达性分析算法 三、垃圾回收算法 a) 标记-清除算法 b) 复制算法 c) 标记-整理算法 d) 分代算法 回收 垃圾回收&#xff08;Garbage Collection&#xff0c;简称GC&#xff09;是…

考研C语言复习初阶(5)

目录 一.表达式求值 1.1隐式类型转换 1.2 算术转换 12.3 操作符的属性 二. 指针是什么&#xff1f; 三 指针和指针类型 3.1 指针-整数 3.2 指针的解引用 3.3 野指针 四.指针运算 4.1 指针-整数 4.2 指针-指针 4.3 指针的关系运算 5. 指针和数组 6. 二级指针 …

使用IAD电话交换机(语音网关)将电话外线对接到FreeSWITCH SIP服务器

在我们初步了解了FreeSWITCH这样的SIP服务器之后&#xff0c;常见的一个需求就是把真实的电信世界&#xff08;比如固话、手机&#xff09;对接到SIP服务器里。 今天我们就介绍一个简单的方法&#xff0c;在3分钟内就把电信局和你的SIP软交换机融合通信起来。 IAD和SIP服务器环…

解决arco-design路由跳转,menu不激活的问题

问题 点击【返回】&#xff0c;路由跳转上一层至首页。左侧菜单栏没有实时更新&#xff0c;激活状态有问题。 解决方法如下&#xff0c;不闪白屏 Main.vue <template><div class"main"><a-layout class"main-layout"><a-layout-…

CH343 使用USB转串口发送CAN报文

文章目录 原启UART 走CAN收发器CH343 模拟CAN发送CPP ASIO SocketCANVXCANGithub Link 原启 早些年自动驾驶激光雷达还不支持PTP之类的时间同步, 很多都是用PPS时间同步, 激光雷达一般装的离控制器或者GNSS天线较远, 车上的线束一般数据电源各种都包在一起的, 如果3.3V直接从域…

私立医院的革命者:大数据解决方案全面解析

第一部分&#xff1a;背景 在信息化飞速发展的今天&#xff0c;医疗行业正经历着一场深刻的数字化转型。特别是对于私立医院来说&#xff0c;要在这个变革的浪潮中立于不败之地&#xff0c;就必须拥抱新技术&#xff0c;优化服务流程&#xff0c;提高医疗质量。大数据技术&…

Python教程-SchemDraw绘制电路图

电路图是电子工程师和电子爱好者的重要工具&#xff0c;用于图形化表示电子元件之间的连接关系。在Python中&#xff0c;有许多库可以用于绘制电路图&#xff0c;其中之一就是SchemDraw。本文将介绍如何使用SchemDraw库&#xff0c;通过简单的Python代码绘制出清晰、美观的电路…

力扣 617-合并二叉树

二叉树使用递归&#xff0c;就要想使用前中后哪种遍历方式&#xff1f; 本题使用哪种遍历都是可以的&#xff01; 我们下面以前序遍历为例。 那么我们来按照递归三部曲来解决&#xff1a; 确定递归函数的参数和返回值&#xff1a; 首先要合入两个二叉树&#xff0c;那么参…

学习java第二天

一.注释 单行注释&#xff1a; // 这是一个单行注释 int x 10; // 初始化一个变量x为10 多行注释&#xff1a; /* 这是一个多行注释 可以用来注释多行代码 */ int y 20; // 初始化一个变量y为20 文档注释&#xff1a; /* 这是一个多行注释 可以用来注释多行代码 */ int…

51单片机基础篇系列-LED灯点亮代码部分

&#x1f308;个人主页: 会编辑的果子君 &#x1f4ab;个人格言:“成为自己未来的主人~” #include<reg52.h> //包含单片机内部寄存器 void main() //&#xff08;&#xff09;{P10xfe;//1111 1110while(1); // } 上面是第一个 LED实验 #include<reg52.h>…

PythonWeb——Django框架

框架介绍 1.什么是框架? 框架就是程序的骨架&#xff0c;主体结构&#xff0c;也是个半成品。 2.框架的优缺点 可重用、成熟,稳健、易扩展、易维护 3.Python中常见的框架 大包大揽 Django被官方称之为完美主义者的Web框架。力求精简web.py和Tornado新生代微框架Flask和B…

GEE python高阶——如何使用geemap和eemont包基于MODIS影像计算GNDVI,NBR,NDWI指数并可视化(山西省太原市为例)

这里我们进行使用geemap和eemont包基于MODIS影像计算GNDVI,NBR,NDWI指数&#xff0c;这里很方便的省去了计算指数、去云和缩放等功能&#xff0c;非常方便。 简介 GNDVI (Green Normalized Difference Vegetation Index)是一种用于评估植被覆盖状况的指数。它是通过测量红光波…