C++17下简化std::tuple状态机transit_to函数的样板代码
问题描述
我在实现有限状态机时,将所有可用状态存储在std::tuple中,原有代码通过手写if分支实现运行时索引到模板实例的分发,每次新增状态都需要手动补充分支,存在重复样板代码。
原有示例代码逻辑如下:
#include <tuple> #include <stdio.h> struct state1 {}; struct state2 {}; struct state3 {}; struct state4 {}; std::tuple<state1, state2, state3, state4> states; template<size_t Index> void transit_to() { auto state = std::get<Index>(states); // 执行状态切换相关逻辑 } void transit_to(size_t index) { if (index == 0) return transit_to<0>(); if (index == 1) return transit_to<1>(); if (index == 2) return transit_to<2>(); if (index == 3) return transit_to<3>(); } int main() { for(int i=0; i<=3; ++i) transit_to(i); }
改造需要满足两个约束:
- 仅使用C17及更低版本的标准特性,禁止使用C20新增语法
- 不修改原有对外接口,不接受改为通过类型访问状态等接口调整方案
实现方案
借助C++17的折叠表达式和std::index_sequence工具,可以自动生成所有索引的分发逻辑,后续新增状态时只需要往tuple里追加类型,不需要手动写分支判断。
完整可运行代码如下:
#include <tuple> #include <cstdio> #include <utility> struct state1 {}; struct state2 {}; struct state3 {}; struct state4 {}; std::tuple<state1, state2, state3, state4> states; template<size_t Index> void transit_to() { auto state = std::get<Index>(states); // 原有状态处理逻辑保持不变 } // 内部分发辅助函数,不对外暴露 template<size_t... Indices> void transit_to_impl(size_t index, std::index_sequence<Indices...>) { // 折叠表达式自动展开生成所有索引的判断分支,匹配到对应索引就调用对应模板实例 ((index == Indices && (transit_to<Indices>(), true)) || ...); } void transit_to(size_t index) { // 自动获取状态tuple的长度,生成0到长度-1的连续索引序列 transit_to_impl(index, std::make_index_sequence<std::tuple_size_v<decltype(states)>>{}); } int main() { for(int i=0; i<=3; ++i) transit_to(i); }
方案特性
- 完全兼容原有接口:对外暴露的两个
transit_to函数签名和原有代码完全一致,上层调用逻辑不需要任何修改 - 零额外开销:编译器优化后生成的汇编代码和手写if分支完全相同,没有运行时性能损失
- 符合版本要求:用到的
std::index_sequence是C14标准特性,折叠表达式、std::tuple_size_v是C17标准特性,没有使用任何C++20新增语法 - 免维护:后续新增、删除状态时,只需要修改
states元组的类型列表,不需要手动调整运行时分发的分支代码
内容的提问来源于stack exchange,提问作者Pablo
相关产品推荐
相关产品推荐

