一文读懂 C++ 14 std::make_index_sequence
ztj100 2025-01-14 19:12 67 浏览 0 评论
一、背景
C++14在标准库里添加了一个很有意思的元函数: std::integer_sequence。并且通过它衍生出了一系列的帮助模板:
- std::make_integer_sequence
- std::make_index_sequence
- 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_sequence 与 std::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 为空。
- make_index_seq<3-1,3-1, M...>时,M 为3-1 = 2
- make_index_seq<2-1,2-1,M...>时,M为2-1=1,2 即序列(1,2)
- make_index_seq<1-1,1-1,M...>时,M为1-1 = 0, 1,2即序列(0,1,2)
- 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的逻辑。
相关推荐
- 前端案例·程序员的浪漫:流星雨背景
-
如果文章对你有收获,还请不要吝啬【点赞收藏评论】三连哦,你的鼓励将是我成长助力之一!谢谢!(1)方式1:简单版本【1】先看实现效果...
- UI样式iPod classic的HTML本地音乐播放器框架
-
PS:音量可以鼠标点击按住在音量图标边的轮盘上下拖拽滑动音量大小中心按钮可以更改播放器为白色...
- JavaScript 强制回流问题及优化方案
-
JavaScript代码在运行过程中可能会强制触发浏览器的回流(Reflow)...
- Ai 编辑器 Cursor 零基础教程:推箱子小游戏实战演练
-
最近Ai火的同时,Ai编辑器Cursor同样火了一把。今天我们就白漂一下Cursor,使用免费版本搞一个零基础教程...
- 19年前司机被沉尸水库!凶手落网,竟已是身家千万的大老板
-
]|\[sS])*"|'(?:[^\']|\[sS])*'|[^)}]+)s*)/g,l=window.testenv_reshost||window.__moon_host||"res.wx.qq...
- 全民健身网络热度调查“居家健身”成为第一网络热词
-
]|\[sS])*"|'(?:[^\']|\[sS])*'|[^)}]+)s*)/g,l=window.testenv_reshost||window.__moon_host||"res.wx.qq...
- 取代JavaScript库的10个现代Web API及详细实施代码
-
为什么浏览器内置的API你还在用某个臃肿的Javascript库呢?用内置的API有什么好处呢?Web平台经历了巨大演进,引入了强大的原生API,不再需要臃肿的JavaScript库。现代浏览器现已支...
- 前端文件下载的N种姿势:从简单到高级
-
文件下载是web开发里一个非常常见的功能,无论是下载用户生成的数据、图片、文档还是应用程序包。前端开发者有多种方式来实现这一需求,每种方式都有其适用场景和优缺点。介绍下几种比较常用的文件下载方法。...
- JavaScript 性能优化方法(js前端性能优化)
-
JavaScript性能优化方法减少DOM操作频繁的DOM操作会导致浏览器重绘和回流,影响性能。使用文档片段(DocumentFragment)或虚拟DOM技术减少直接操作。...
- DOM节点的创建、插入、删除、查找、替换
-
在前端开发中,js与html联系最紧密的莫过于对DOM的操作了,本文为大家分享一些DOM节点的基本操作。一、创建DOM节点使用的命令是varoDiv=document.createElement...
- 前端里的拖拖拽拽(拖拽式前端框架)
-
最近在项目中使用了react-dnd,一个基于HTML5的拖拽库,“拖拽能力”丰富了前端的交互方式,基于拖拽能力,会扩展各种各样的拖拽反馈效果,因此有必要学习了解,最好的学习方式就是实操!...
- 大模型实战:Flask+H5三件套实现大模型基础聊天界面
-
本文使用Flask和H5三件套(HTML+JS+CSS)实现大模型聊天应用的基本方式话不多说,先贴上实现效果:流式输出:思考输出:聊天界面模型设置:模型设置会话切换:前言大模型的聊天应用从功能...
- SSE前端(sse前端数据)
-
<!DOCTYPEhtml><htmllang="zh-CN"><head>...
- 课堂点名总尴尬?试试 DeepSeek,或能实现点名自由!(附教程)
-
2025年2月26日"你有没有经历过这样的场景?老师拿着花名册扫视全班:'今天我们来点名...'那一刻心跳加速,默念:'别点我!'但现在,我要...
- 我会在每个项目中复制这10个JS代码片段
-
你是否也有这种感觉:在搭建新项目时,你会想:"这个函数我是不是已经写过了...在某个地方?"是的——我也是。所以在开发了数十个React、Node和全栈应用后,我不再重复造轮子。我创建...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)