如何在访问std::variant时获取编译期索引并实现变体活跃索引递增?
如何在访问std::variant时获取编译期索引并实现变体活跃索引递增?
你遇到的问题确实很常见——想要在处理std::variant的活跃对象时,同时拿到它的编译期索引,这样就能安全切换到下一个类型,又不想从头实现一套类似std::visit的复杂逻辑。其实不用重写完整的visit,我们可以用一些轻量技巧实现需求,还能避免多次索引查找的开销。
方法:基于索引序列实现轻量的带索引访问
借助C++20的模板lambda和std::index_sequence,我们可以写一个极简的visit_with_index辅助函数,它基于标准库的std::visit实现,不需要自己处理复杂的重载决议:
#include <variant> #include <array> #include <utility> template <typename Visitor, typename... Ts> decltype(auto) visit_with_index(Visitor&& vis, std::variant<Ts...>& var) { return std::visit([&]<size_t... Is>(std::index_sequence<Is...>) { // 把访问器的每个编译期索引版本的operator()地址存入constexpr数组 using FuncType = decltype(&std::forward<Visitor>(vis).template operator()<Is>); constexpr std::array<FuncType, sizeof...(Ts)> handler_table = { &std::forward<Visitor>(vis).template operator()<Is>... }; // 仅做一次运行时索引查找,调用对应编译期索引的处理函数 return handler_table[var.index()](std::get<Ts>(var)); }, std::make_index_sequence<sizeof...(Ts)>()); }
适配你的原有逻辑
现在你可以直接在update_variant里使用这个辅助函数,和你原来的代码逻辑完全兼容,而且只做了一次索引查找:
template <typename... Ts> bool update_variant(std::variant<Ts...>& v, std::tuple<work_generator<Ts>>& gen) { return visit_with_index([&]<size_t Index>(auto& e) { if (e.done()) { constexpr size_t next_index = Index + 1; if constexpr (next_index == sizeof...(Ts)) { // 所有类型都已处理完毕 return false; } else { // 切换到下一个variant类型 v.emplace( std::in_place_index_t<next_index>{}, std::get<next_index>(gen).create_worker() ); return true; } } else { // 处理当前对象 e.do_some_work(); return true; } }, v); }
原理说明
这个方法的核心是:
- 利用
std::index_sequence生成std::variant所有可能的编译期索引序列 - 把访问器针对每个索引的处理函数地址存入一个
constexpr数组(编译期就确定了布局) - 通过
var.index()做一次运行时查找,直接调用对应编译期索引的处理函数,避免了多次索引查询的开销
整个实现依赖C++20的模板lambda特性,当前主流编译器(GCC、Clang、MSVC)都已支持该标准特性。
备注:内容来源于stack exchange,提问作者Bernard




