std::variant与std::shared_ptr的operator==特性冲突及自定义实现难题
问题场景
当std::variant中包含std::shared_ptr类型时,标准库自动生成的operator==会使用std::shared_ptr默认的指针地址比较,而非指向的值比较——这和variant中其他类型(如std::vector、基础类型)的值比较行为不一致,不符合业务预期。
简化示例代码
#include <iostream> #include <string> #include <vector> #include <variant> #include <memory> using VecInt = std::vector<int>; using VecStr = std::vector<std::string>; using VarFoo = std::variant<std::monostate,int>; using VarBar = std::variant<std::monostate,VecStr,VecInt>; using PtrInt = std::shared_ptr<int>; using VarBaz = std::variant<std::monostate,PtrInt>; int main() { VecInt v1{1,2,3}; VecInt v2{1,2,3}; std::cout << "v1 == v2 ? " << std::boolalpha << (v1 == v2) << std::endl; VecStr v3{"Hello","World"}; VecStr v4{"Hello","World"}; std::cout << "v3 == v4 ? " << std::boolalpha << (v3 == v4) << std::endl; VarFoo foo1{42}; VarFoo foo2{42}; std::cout << "foo1 == foo2 ? " << std::boolalpha << (foo1 == foo2) << std::endl; VarBar bar1{VecInt{42,43}}; VarBar bar2{VecInt{42,43}}; std::cout << "bar1 == bar2 ? " << std::boolalpha << (bar1 == bar2) << std::endl; VarBaz baz1{std::make_shared<int>(42)}; VarBaz baz2{std::make_shared<int>(42)}; std::cout << "baz1 == baz2 ? " << std::boolalpha << (baz1 == baz2) << std::endl; return 0; }
输出结果
v1 == v2 ? true
v3 == v4 ? true
foo1 == foo2 ? true
bar1 == bar2 ? true
baz1 == baz2 ? false
自定义operator==的困境
直接重载operator==会遇到两个问题:要么和标准库生成的签名冲突导致链接错误,要么在默认分支调用自身造成无限递归:
#include <variant> #include <memory> using DemoVar = std::variant<std::monostate,int,std::shared_ptr<int>>; constexpr bool operator==(const DemoVar& v, const DemoVar& w) { if (v.index() == w.index()) { switch (v.index()) { case 2: // custom shared-pointer value comparison // ... return <whatever_it_takes>; default: return v == w; // 无限递归!无法区分自定义和标准运算符 } } return false; }
解决方案
方案1:使用命名比较函数(推荐)
放弃重载operator==,改用自定义命名函数(如value_equal),避免和标准库冲突,实现简单且安全:
#include <variant> #include <memory> #include <type_traits> using DemoVar = std::variant<std::monostate, int, std::shared_ptr<int>>; // 默认模板:使用类型自身的== template <typename T> constexpr bool value_compare(const T& a, const T& b) { return a == b; } // 特化std::shared_ptr的比较逻辑:比较指向的值 template <typename T> constexpr bool value_compare(const std::shared_ptr<T>& a, const std::shared_ptr<T>& b) { if (!a && !b) return true; if (!a || !b) return false; return *a == *b; } // 对variant应用自定义比较 constexpr bool value_equal(const DemoVar& v, const DemoVar& w) { if (v.index() != w.index()) return false; return std::visit([](const auto& lhs, const auto& rhs) { return value_compare(lhs, rhs); }, v, w); }
使用时直接调用value_equal(baz1, baz2)即可,无需枚举variant内的所有类型,同时针对std::shared_ptr做特殊处理,其他类型保持默认行为。
方案2:重载operator==(规避递归)
如果必须重载operator==,可以借助std::visit和constexpr if实现类型分支处理,同时通过辅助命名空间调用标准库运算符避免递归:
#include <variant> #include <memory> #include <type_traits> // 辅助命名空间:引入标准库的operator==,用于ADL查找 namespace std_variant_ops { using std::operator==; } using DemoVar = std::variant<std::monostate, int, std::shared_ptr<int>>; constexpr bool operator==(const DemoVar& v, const DemoVar& w) { if (v.index() != w.index()) return false; return std::visit([](const auto& lhs, const auto& rhs) -> bool { using T = std::decay_t<decltype(lhs)>; if constexpr (std::is_same_v<T, std::shared_ptr<int>>) { // 自定义shared_ptr的值比较逻辑 if (!lhs && !rhs) return true; if (!lhs || !rhs) return false; return *lhs == *rhs; } else { // 调用标准库的operator==,避免递归 return std_variant_ops::operator==(lhs, rhs); } }, v, w); }
总结
- 优先选择命名比较函数:实现简单、无冲突风险,代码可读性更强。
- 若必须重载
operator==:利用std::visit遍历类型,constexpr if分支处理,通过辅助命名空间调用标准运算符规避递归。
内容的提问来源于stack exchange,提问作者BitTickler
相关产品推荐
相关产品推荐

