Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid forgetting to define operator== in subclasses?

I have a base class and define a operator== on it. And B is a subclass of A and I forget to define operator== on B. Then A::operator== is used on comparing B and usually this gives an unexpected results. Any good method to avoid such "forget"? I add an example to clarify my question.

class A
{
public:
    bool operator==(const A& rhs) const
    {
        return i == rhs.i;
    }

    int i
};

class B : public A
{
public:
    int j;
}

B b1, b2;
b1.i = 1; b1.j = 2;
b2.i = 1; b1.j = 3;
bool b = (b1 == b2); // will be true
like image 216
user1899020 Avatar asked Jan 13 '23 09:01

user1899020


2 Answers

What you could try is put A in a namespace, create operator == as a template non-member also in that namespace and let ADL take care of it.

#include <iostream>

namespace stuff {
class A
{
};

class B : public A {};

template <typename T>
bool operator == (const T &lhs, const T &rhs)
{
  std::cout << __PRETTY_FUNCTION__ << '\n';
  return &lhs == &rhs; // <-- replace this with something real
}

}

struct C {};

int main()
{
  stuff::A a, aa;
  stuff::B b, bb;
  C c, cc;

  b == bb;
  aa == a;

  aa == cc; // error: no match for "operator==" stuff::A and C
  b == a;   // error: no match for "operator==" stuff::B and stuff::A
}

Edit: For your edited example where you want the equality check to compare each part of the class with the other respective corresponding part, DyP's suggestion can work. For example:

// same as before
// ...
class A
{
public:
  bool is_equal(const A &rhs) const { return i == rhs.i; }
};

class B : public A
{
public:
  bool is_equal(const B &rhs) const { return A::is_equal(rhs) && (j == rhs.j); }
};

template <typename T>
bool operator == (const T &lhs, const T &rhs)
{
  std::cout << __PRETTY_FUNCTION__ << '\n';
  return lhs.is_equal(rhs);
}

Now comparing this again in the using code:

// ...
b.i = 1, bb.i = 1;
b.j = 1, bb.j = 42;
cout << boolalpha << (b == bb) << '\n';
b.j = 42;
cout << (b == bb) << '\n';

a.i = 2, aa.i = 3;
cout << (aa == a) << '\n';    

outputs:

bool stuff::operator==(const T&, const T&) [with T = stuff::B]
false
bool stuff::operator==(const T&, const T&) [with T = stuff::B]
true
bool stuff::operator==(const T&, const T&) [with T = stuff::A]
false
like image 117
greatwolf Avatar answered Jan 21 '23 18:01

greatwolf


Allowing implicit conversions for greatwolf's great approach is a bit tricky:

#include <type_traits>

namespace stuff
{

    template<class T, class U>
    bool operator== (const T &lhs, const U &rhs)
    {
        using namespace std;
        static_assert(is_convertible<T, U>{} || is_convertible<U, T>{},
                      "invalid argument type");
        static_assert
        (
               is_same<T, U>{}
            || ( not is_base_of<T, U>{} && not is_base_of<U, T>{})
            , "use explicit casts to compare derived to base class types"
        );
        return is_equal(lhs, rhs);
    }

    template<class T>
    bool is_equal(T const&, T const&)
    {
        // force compile-time failure when instantiating
        static_assert(std::is_same<T, void>{},
          "no free is_equal function for these argument types available");

        return false;
    }

    class A
    {
    private:
        int i;

        friend bool is_equal(A const& lhs, A const& rhs)
        { return lhs.i == rhs.i; }

    public:
        A(int p_i) : i(p_i) {}
    };

    class B : public A
    {
        int j;

    public:
        B(int p_i, int p_j) : A(p_i), j(p_j) {}
    };

    class C : public A
    {
    private:
        int j;

        friend bool is_equal(C const& lhs, C const& rhs)
        {
            return    is_equal(static_cast<A const&>(rhs),
                               static_cast<A const&>(lhs))
                   && lhs.j == rhs.j;
        }

    public:
        C(int p_i, int p_j) : A(p_i), j(p_j) {}
    };

}

struct D
{
    operator stuff::C() const
    {
        return stuff::C(1, 42);
    }
};

#include <iostream>
int main()
{
    stuff::A a(1), aa(1);
    stuff::B b(1, 42), bb(1, 42);
    stuff::C c(1, 42), cc(1, 42);

    D d;

    // commented lines invoke compilation failures
    std::cout << "a == aa: " << (a == aa) << std::endl;
  //std::cout << "a == b : " << (a == b ) << std::endl;
  //std::cout << "b == bb: " << (b == bb) << std::endl;
  //std::cout << "a == c : " << (a == c ) << std::endl;
    std::cout << "c == cc: " << (c == cc) << std::endl;
    std::cout << "d == c : " << (d == c ) << std::endl;
}
like image 32
dyp Avatar answered Jan 21 '23 18:01

dyp