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
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
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With