You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在访问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

火山引擎 最新活动