Home > Net >  operator== idiosyncrasies between std::variant and std::shared_ptr?
operator== idiosyncrasies between std::variant and std::shared_ptr?

Time:09-01

The automatically instantiated operator== for my std::variant<> interferes with one of its variations, which is a std::shared_ptr<>. My actual variant has about 12 different possible alternatives, but to keep it short, here I just show a trivial example:

#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;
}

Which produces the output:

v1 == v2 ? true
v3 == v4 ? true
foo1 == foo2 ? true
bar1 == bar2 ? true
baz1 == baz2 ? false

The problem being, that the semantics of std::shared_ptr<> is to simply compare the pointer, but not go beyond that to compare the values pointed to, while the rest (std::vector<>, std::variant<>,...) actually compare values.

Of course, if you just use a single pointer, it might be reasonable to assume, that operator==() should only compare the pointer values. But in the context of a variant, this makes no real sense.

Now, with some fictive variant, containing shared pointers, one would have to write ones own override of operator==(), but with many variations in it, this becomes cumbersome quickly. The shortcut being to compare the index() of the shared pointer entries and give them special handling, while resorting for the other possibilities to the existing operator==. But there is the catch:

// the standard, e.g. see https://en.cppreference.com/w/cpp/utility/variant/operator_cmp
template< class... Types >
constexpr bool operator==( const std::variant<Types...>& v,
                           const std::variant<Types...>& w );

The signature of a self written operator== for my variant will have the same function signature as the one automatically instantiated from the template in the standard. So, we either get linker errors (and factually cannot do a custom override for variants) or we have some infinite recursions in code like this:

#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; // bad idea! infinite recursion! Conundrum! No way to differ between    
                     // our custom and the automatically instantiated operator==!
    }
  }
  return false;
}

So, any idea, how to achieve a custom operator== for a variant, without having to type out all the cases? Or should I better not use operator== at all and give the compare function another name entirely?

CodePudding user response:

std::shared_ptr<T>::operator== compares the pointers. Thats what it should do (irrespective of being in a std::variant or not). If you want to compare the pointees, then you do not want std::shared_ptr, at least not a naked one. You can use a custom class:

 struct my_shared_int {
     std::shared_ptr<int> value;
     // constructor / copy / move / assignment as desired
     bool operator==(const my_shared_int& other) {
         // compare the pointees
         // (after checking that values arent empty)
     }
 };

The generated std::variant<std::monostate,my_shared_int>::operator== will then compare the integers not the pointers.

  •  Tags:  
  • c
  • Related