如何为存储std::variant的std::map包装类实现多态operator[]
问题解答
类型推导时机:必须在运行时完成
C++是静态类型语言,编译阶段无法知晓std::map中某个key对应的std::variant<A,B>到底存储的是A还是B——因为这个类型是运行时动态确定的(比如你可以在程序运行时随时给某个key赋值A或B)。所以不可能在编译时直接返回A/B类型的对象,所有类型匹配和提取逻辑都要在运行时处理。
实现方案
要实现你期望的A a = m[1]这种写法,有两种常见思路:
方案1:返回代理类,支持隐式转换到A/B
通过让operator[]返回一个代理对象,这个代理类重载了到A和B的隐式转换运算符,在转换时自动调用std::get提取对应类型,类型不匹配时抛出标准异常。
完整代码:
#include <map> #include <variant> #include <string> #include <stdexcept> struct A { std::string val; }; struct B { int val; }; // 代理类,用于包装variant并提供隐式转换 struct MyMapProxy { std::variant<A, B>& var_ref; // 隐式转换为A(值语义) operator A() const { if (!std::holds_alternative<A>(var_ref)) { throw std::bad_variant_access{}; } return std::get<A>(var_ref); } // 隐式转换为B(值语义) operator B() const { if (!std::holds_alternative<B>(var_ref)) { throw std::bad_variant_access{}; } return std::get<B>(var_ref); } // 隐式转换为A的引用(避免拷贝) operator A&() { if (!std::holds_alternative<A>(var_ref)) { throw std::bad_variant_access{}; } return std::get<A>(var_ref); } // 隐式转换为B的引用(避免拷贝) operator B&() { if (!std::holds_alternative<B>(var_ref)) { throw std::bad_variant_access{}; } return std::get<B>(var_ref); } }; struct MyMap { std::map<int, std::variant<A, B>> internalMap; // 非const版本的operator[] MyMapProxy operator[](int key) { return {internalMap[key]}; } // const版本的operator[],找不到key时抛出异常 const MyMapProxy operator[](int key) const { auto it = internalMap.find(key); if (it == internalMap.end()) { throw std::out_of_range("Key not present in MyMap"); } return {const_cast<std::variant<A, B>&>(it->second)}; } }; // 测试示例 int main() { MyMap m; m.internalMap[1] = A{"test_string"}; m.internalMap[2] = B{123}; // 直接赋值,自动完成类型转换 A a = m[1]; B b = m[2]; // 直接修改原对象(通过引用转换) m[1].val = "updated"; m[2].val = 456; // 错误用法:类型不匹配会抛出std::bad_variant_access // A wrong = m[2]; return 0; }
方案2:模板化成员函数,显式指定提取类型
如果希望编译时就明确要提取的类型(避免隐式转换的潜在风险),可以实现一个模板化的get成员函数,让用户显式指定要获取的类型。这种写法更清晰,也能让编译器帮你做部分类型检查(比如不能指定非A/B的类型)。
代码示例:
#include <map> #include <variant> #include <string> #include <stdexcept> struct A { std::string val; }; struct B { int val; }; struct MyMap { std::map<int, std::variant<A, B>> internalMap; // 非const版本:获取指定类型的引用 template<typename T> T& get(int key) { auto& var = internalMap[key]; if (!std::holds_alternative<T>(var)) { throw std::bad_variant_access{}; } return std::get<T>(var); } // const版本:获取指定类型的const引用 template<typename T> const T& get(int key) const { auto it = internalMap.find(key); if (it == internalMap.end()) { throw std::out_of_range("Key not present in MyMap"); } const auto& var = it->second; if (!std::holds_alternative<T>(var)) { throw std::bad_variant_access{}; } return std::get<T>(var); } }; // 测试示例 int main() { MyMap m; m.internalMap[1] = A{"test_string"}; m.internalMap[2] = B{123}; // 显式指定类型获取引用 A& a = m.get<A>(1); B& b = m.get<B>(2); a.val = "updated"; b.val = 456; // 错误用法:类型不匹配会抛出异常 // A& wrong = m.get<A>(2); return 0; }
关键注意点
- 两种方案在类型不匹配时都会抛出
std::bad_variant_access异常,你可以根据需求替换为自定义的错误处理逻辑(比如返回std::optional)。 - 如果想避免异常,可以先调用
std::holds_alternative检查类型,再进行提取:
auto it = m.internalMap.find(1); if (it != m.internalMap.end() && std::holds_alternative<A>(it->second)) { A& a = std::get<A>(it->second); // 处理a }
内容的提问来源于stack exchange,提问作者TheMemeMachine
相关产品推荐
相关产品推荐

