如何无需std::visit复用非const实现,将variant转为对应const版本?
问题
- 能否不使用
std::visit或等效代码,基于非const版本的foobar_manager::get_foobar()实现const版本的const_foobar foobar_manager::get_foobar() const? - 是否存在更简便的方法将一种variant转换为包含相同类型const版本的另一种variant?
示例代码
#include <variant> #include <string> struct foo { int val; std::string str; }; struct bar { double val; std::string str; }; using foobar = std::variant<foo, bar>; using const_foobar = std::variant<const foo, const bar>; class foobar_manager { public: foobar get_foobar() { // 这里是获取variant的核心逻辑 return foo{ 42, "mumble" }; } const_foobar get_foobar() const { // 希望复用非const版本的逻辑,能否不用std::visit? return std::visit( [](auto v)->const_foobar { return v; }, const_cast<foobar_manager*>(this)->get_foobar() ); } }; int main() { foobar_manager fm; const auto& const_fm = fm; auto test1 = fm.get_foobar(); auto test2 = const_fm.get_foobar(); return 0; }
解答
1. 不使用std::visit实现const版本的get_foobar
可以做到,通过手动判断variant的活跃类型索引,逐个处理每个类型分支即可:
修改const版本的get_foobar如下:
const_foobar get_foobar() const { // 注意:仅当非const版本的get_foobar不会修改对象状态时,const_cast才安全 auto non_const_var = const_cast<foobar_manager*>(this)->get_foobar(); switch(non_const_var.index()) { case 0: // 对应foo类型 return const_foobar{std::get<foo>(non_const_var)}; case 1: // 对应bar类型 return const_foobar{std::get<bar>(non_const_var)}; default: throw std::bad_variant_access{}; } }
这种方式避开了std::visit,但缺点是如果foobar的类型列表发生变化(比如新增类型),必须同步修改switch分支,维护成本较高。
更安全的替代方案:抽离核心逻辑
上面的const_cast存在风险——如果非const版本的get_foobar会修改对象状态,对const对象调用它就是未定义行为。更稳妥的做法是把获取variant的核心逻辑抽成私有const成员函数,让两个版本的get_foobar都调用它:
class foobar_manager { private: // 核心逻辑:只填充传入的variant,不修改对象自身 void populate_foobar(foobar& out) const { out = foo{42, "mumble"}; } public: foobar get_foobar() { foobar res; populate_foobar(res); return res; } const_foobar get_foobar() const { foobar res; populate_foobar(res); switch(res.index()) { case 0: return const_foobar{std::get<foo>(res)}; case 1: return const_foobar{std::get<bar>(res)}; default: throw std::bad_variant_access{}; } } };
2. 简化variant到const版本的转换
标准库没有直接提供这种转换工具,但可以封装一个通用模板函数,基于std::visit实现一次后复用:
// 通用转换函数:将std::variant<Ts...>转为std::variant<const Ts...> template<typename... Ts> std::variant<const Ts...> to_const_variant(std::variant<Ts...> var) { return std::visit( [](auto&& val) -> std::variant<const Ts...> { return std::forward<decltype(val)>(val); }, std::move(var) ); }
之后在const版本的get_foobar里直接调用这个函数,代码更简洁:
const_foobar get_foobar() const { auto non_const_var = const_cast<foobar_manager*>(this)->get_foobar(); return to_const_variant(std::move(non_const_var)); }
如果用了抽离核心逻辑的方案,也可以这样用:
const_foobar get_foobar() const { foobar res; populate_foobar(res); return to_const_variant(std::move(res)); }
这个通用函数可以处理任意类型列表的variant到其const版本的转换,无需重复编写转换逻辑。
内容的提问来源于stack exchange,提问作者jwezorek
相关产品推荐
相关产品推荐

