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;
}
Why do you have equality comparison in a class hierarchy? In many cases, this indicates a problem with the design, with classes that don't properly behave like value types, but not properly like objects from a hierarchy either.
Related
I have the following class hierarchy plus a wrapper class:
struct Base {
virtual ~Base() = default;
virtual void init() = 0;
};
struct DerivedA : Base {
void init() override {
// do something specific to DerivedA
}
};
struct DerivedB : Base {
void init() override {
// do something specific to DerivedB
}
};
struct Wrapper{
Base* _item;
};
Now, I would like to have a container of Wrapper (e. g. std::vector<Wrapper>) and a function f which expects two derived classes as parameters, like this:
void f(DerivedA* d1, DerivedA* d2) {
std::cout << "A vs A" << std::endl;
}
void f(DerivedA* d1, DerivedB* d2) {
std::cout << "A vs B" << std::endl;
}
void f(DerivedB* d1, DerivedB* d2) {
std::cout << "B vs B" << std::endl;
}
void f(DerivedB* d1, DerivedA* d2) {
std::cout << "B vs A" << std::endl;
}
int main() {
std::vector<Wrapper> items;
items.emplace_back(new DerivedA());
items.emplace_back(new DerivedB());
items.emplace_back(new DerivedB());
items.emplace_back(new DerivedA());
for (auto i = 0; i < items.size(); i++) {
for (auto j = i+1; j < items.size(); j++) {
// do something wizh all item pairs
f(items[i]._item, items[j]._item);
}
}
return 0;
}
This does not compile because the compiler does expect a declaration of f(Base* b1, Base* b2), which of course makes sense.
What I tried so far:
Use enum class DerivedType { A, B, C }; to decipher the type of the derived class. This works but seems clumsy.
Use a templated version of Wrapper and store each template type in a different container, like std::vector<Wrapper<DerivedX>>. Again, this feels like I'm doing something wrong.
Use std::variant<DerivedA, DerivedB> _item in place of Base* _item in Wrapper. But I could not get std::visit to return a pointer to a dervied class the way I require it for f (because operator()(DerivedX* d) expects the same return to for any X).
What are possible ways to implement this? Does the solution I am looking for even exist?
Using an RTTI-based solution is also an option:
void f1(Base* d1, Base* d2) {
const auto& t1 = typeid(*d1);
const auto& t2 = typeid(*d2);
if (t1 == typeid (DerivedA) && t2 == typeid (DerivedA)) {
std::cout << "A vs A" << std::endl;
}
if (t1 == typeid (DerivedA) && t2 == typeid (DerivedB)) {
std::cout << "A vs B" << std::endl;
}
if (t1 == typeid (DerivedB) && t2 == typeid (DerivedA)) {
std::cout << "B vs A" << std::endl;
}
if (t1 == typeid (DerivedB) && t2 == typeid (DerivedB)) {
std::cout << "B vs B" << std::endl;
}
}
With std::variant, it would be:
struct Wrapper{
std::variant<DerivedA, DerivedB> _item;
};
int main() {
std::vector<Wrapper> items {
{ DerivedA()},
{ DerivedB()},
{ DerivedB()},
{ DerivedA()}
};
for (auto i = 0; i < items.size(); i++) {
for (auto j = i+1; j < items.size(); j++) {
// do something with all item pairs here
std::visit([] (auto& lhs, auto& rhs) { f(&lhs, &rhs); }, items[i]._item, items[j]._item);
}
}
}
Given the following code:
#include <iostream>
using namespace std;
class B {
private:
int n;
public:
B(int x) :
n(x) {
}
B operator+(B& b) {
return B(n + b.n);
}
friend ostream& operator<<(ostream &out, const B& b) {
out << "B: " << b.n;
return out;
}
bool operator<(const B& rhs) const {
return n < rhs.n;
}
};
int main() {
B b1(1), b2(2), b3(3);
const B b4 = b1 + (b2 + b3); // error
cout << b4 << b1 << b2 << b3;
return 0;
}
I know that if I will change this :
B operator+(B& b)
To:
B operator+(const B& b)
So it's will be ok. But, why it's really fix it?
invalid initialization of non-const reference of type 'B&' from an rvalue of type 'B'
(I understand the meaning of the error, but, I don't understand why the changing fix the error).
In addition, what is the difference between the code above (that give me error), to the following code:
class D: public B {
double m;
public:
D() :
B(0), m(0) {
}
D(int x, int y) :
B(x), m(y) {
}
D operator+(D& d) { // ~~
return d;
}
friend ostream& operator<<(ostream &out, const D& d) {
out << static_cast<const B&>(d);
out << ", D: " << d.m;
return out;
}
};
int main() {
B b4(4);
D d1(1, 0), d2(1, 2);
D d3 = d1 + d2;
cout << b4 << ", " << d3 << endl;
return 0;
}
Why now I don't get at ~~ also error?
In the past I reduced the repetition in operator<, <=, > and >= functions of data type classes by writing a helper function which takes a comparison function as argument.
Simplified, a such class looks like this:
class Foo {
public:
bool comparison(const Foo &other, std::function<bool(int a, int b)> compFn) {
if (_a == other._a) {
if (_b == other._b) {
return compFn(_c, other._c);
}
return compFn(_b, other._b);
}
return compFn(_a, other._a);
}
bool operator<(const Foo &other) const {
return comparison(other, [](int a, int b){return a<b;})
}
bool operator<=(const Foo &other) const {
return comparison(other, [](int a, int b){return a<=b;})
}
bool operator>(const Foo &other) const {
return comparison(other, [](int a, int b){return a>b;})
}
bool operator>=(const Foo &other) const {
return comparison(other, [](int a, int b){return a>=b;})
}
private:
int _a;
int _b;
int _c;
};
The real classes use complexes data types, but the principle stays the same. The comparison less or greater is a comparison of a number of attributes in a given order.
Using the comparison function I can change this order at any time, just rewriting a single function. Also the logic is at a single point and not repeated in four places.
Is there a established design pattern to cover this situation?
I realised that there are already the comparison objects, like std::less. But I could not find a simple way to use them instead instead of the shown solution.
Can the shown solution further simplified using other language features or the standard library?
Your implementation looks a bit complicated for me...
I would try to solve the problem using std::tie as follows:
class Foo {
public:
bool operator<(const Foo &other) const {
return std::tie(_a, _b, _c) <
std::tie(other._a, other._b, other._c);
}
bool operator<=(const Foo &other) const {
return std::tie(_a, _b, _c) <=
std::tie(other._a, other._b, other._c);
}
bool operator>(const Foo &other) const {
return !(*this <= other);
}
bool operator>=(const Foo &other) const {
return !(*this < other);
}
private:
int _a;
int _b;
int _c;
};
You can use Curiously Recurring template pattern in combination with Barton-Nackman trick.
You only need to define 1 comparation function in your classes that returns:
< 0 if a < b
> 0 if a > b
== 0 if a == b
When a class template is instantiated the in-class friend definitions produce operator definitions.
Normal Runtime polymorphism without CRTP would also work.
You can change the comparation logic in the Cmp(...) however you want, and the operator functions are playing nicely. I provided also small example: one default and inverted comparation.
#include <iostream>
#include <iomanip>
#include <tuple>
using namespace std;
template<typename T>
struct Comparable {
friend bool operator==(const T& lft, const T& rgt) {
return lft.Cmp(rgt) == 0;
}
friend bool operator<(const T& lft, const T& rgt) {
return lft.Cmp(rgt) < 0;
}
friend bool operator>(const T& lft, const T& rgt) {
return lft.Cmp(rgt) > 0;
}
friend bool operator!=(const T& lft, const T& rgt) {
return !operator==(lft, rgt);
}
friend bool operator<=(const T& lft, const T& rgt) {
return !operator>(lft, rgt);
}
friend bool operator>=(const T& lft, const T& rgt) {
return !operator<(lft, rgt);
}
};
class Foo : public Comparable<Foo> {
private:
static int cmpMode_;
public:
static void SetDefaultComparator() { cmpMode_ = 1; }
static void SetInvertedComparator() { cmpMode_ = -1; }
public:
Foo() : a_{0}, b_{0}, c_{0} {}
Foo(int a, int b, int c) : a_{a}, b_{b}, c_{c} {}
// return < 0 if lhs < rhs
// return == 0 if lhs == rhs
// return > 0 if lhs > rhs
int Cmp(const Foo& rhs) const {
auto t1 = std::tie(a_, b_, c_);
auto t2 = std::tie(rhs.a_, rhs.b_, rhs.c_);
auto cmp = t1 < t2 ? -1 : (t1 > t2 ? 1 : 0);
return cmpMode_ * cmp;
}
private:
int a_;
int b_;
int c_;
}; // Foo
int Foo::cmpMode_ = 1;
int main() {
Foo f1{1,1,1};
Foo f2{1,1,1};
Foo f3{1,1,2};
Foo f4{1,2,1};
Foo::SetDefaultComparator();
cout << endl << "Using default comparator: " << endl;
cout << boolalpha
<< "f1 == f2 ? " << (f1 == f2) << endl
<< "f1 != f2 ? " << (f1 != f2) << endl
<< "f2 < f3 ? " << (f2 < f3) << endl
<< "f2 <= f3 ? " << (f2 <= f3) << endl
<< "f3 > f4 ? " << (f3 > f4) << endl
<< "f3 >= f4 ? " << (f3 >= f4) << endl
<< "f4 > f3 ? " << (f4 >= f4) << endl;
Foo::SetInvertedComparator();
cout << endl << "Using inverted comparator: " << endl;
cout << boolalpha
<< "f1 == f2 ? " << (f1 == f2) << endl
<< "f1 != f2 ? " << (f1 != f2) << endl
<< "f2 < f3 ? " << (f2 < f3) << endl
<< "f2 <= f3 ? " << (f2 <= f3) << endl
<< "f3 > f4 ? " << (f3 > f4) << endl
<< "f3 >= f4 ? " << (f3 >= f4) << endl
<< "f4 > f3 ? " << (f4 >= f4) << endl;
}
Here the live demo: https://wandbox.org/permlink/uteFqj0DhSaBqxfb
You need operator spaceship '<=>' which is scheduled into c++2x, I think. :-) Real name threeWay.
Certainly, that would be the right pattern to use, as you can migrate to it when it gets here.
Basically, <=> returns 0 if the arguments are equal, -1 if they compare "less", +1 if they are "greater". See strcmp() for an example.
You can either choose to implement <=> using = and >, then all your other operators call <=>, or implement <=> directly and have /all/ other operators simply call that.
Consider this simple class storing a value and a time.
class A
{
public:
boost::posix_time::ptime when;
double value;
};
Depending on the context, I need to compare two instances of A by value or by time (and/or store them in set/map, sometimes sorted by value, sometimes by time).
Providing operator< will be confusing, because you can't tell if it will compare by value or by time.
Now, what's the best strategy?
Is it possible to provide an operator< taking a parameter? (would be used as a <(ByTime) b)?
Should I have a lowerThan (comparing values) method and a earlierThan (comparing time) method taking the right operand as parameter? But then, what would be the best practice to handle <, <=, >, >=, ==, !=, should I have one method for each comparator? Or may they take parameters (like bool isLower(bool strict, const A& right) const, bool isGreater(bool strict, const A& right) const, bool isEarlier(bool strict, const A& right) const, bool isLater(bool strict, const A& right) const...
What would be the best practice?
IMHO the most versatile way is a 2-step process:
make ADL getters.
write comparison concepts in terms of those getters.
example:
#include <boost/date_time.hpp>
#include <set>
#include <vector>
#include <algorithm>
class A
{
public:
boost::posix_time::ptime when;
double value;
};
// get the 'when' from an A
auto get_when(A const& a) -> boost::posix_time::ptime
{
return a.when;
}
// get the 'when' from a ptime (you could put this in the boost::posix_time namespace for easy ADL
auto get_when(boost::posix_time::ptime t) -> boost::posix_time::ptime
{
return t;
}
// same for the concept of a 'value'
auto get_value(A const& a) -> double
{
return a.value;
}
auto get_value(double t) -> double
{
return t;
}
// compare any two objects by calling get_when() on them
struct increasing_when
{
template<class L, class R>
bool operator()(L&& l, R&& r) const
{
return get_when(l) < get_when(r);
}
};
// compare any two objects by calling get_value() on them
struct increasing_value
{
template<class L, class R>
bool operator()(L&& l, R&& r) const
{
return get_value(l) < get_value(r);
}
};
void example1(std::vector<A>& as)
{
// sort by increasing when
std::sort(begin(as), end(as), increasing_when());
// sort by increasing value
std::sort(begin(as), end(as), increasing_value());
}
int main()
{
// same for associative collections
std::set<A, increasing_when> a1;
std::set<A, increasing_value> a2;
}
update:
If you want, you can templatise the comparison:
template<class Comp>
struct compare_when
{
template<class L, class R>
bool operator()(L&& l, R&& r) const
{
return comp(get_when(l), get_when(r));
}
Comp comp;
};
using increasing_when = compare_when<std::less<>>;
using decreasing_when = compare_when<std::greater<>>;
to use the comparison directly in code:
auto comp = compare_when<std::greater<>>();
if (comp(x,y)) { ... }
Reacting to UKMonkey comment, would defining what I understand could be named "comparator classes" be a good approach/practice?
class A
{
public:
boost::posix_time::ptime when;
double value;
const boost::posix_time::ptime& getTime() const { return when; }
double getValue() const { return value; }
};
template <typename T>
class CompareBy
{
public:
CompareBy( const A& a, T (A::*getter)() const ) : a(a), getter(getter)
{}
bool operator<( const CompareBy& right ) const
{
return (a.*getter)() < (right.a.*getter)();
}
// you may also declare >, <=, >=, ==, != operators here
private:
const A& a;
T (A::*getter)() const;
};
class CompareByTime : public CompareBy<const boost::posix_time::ptime&>
{
public:
CompareByTime(const A& a) : CompareBy(a, &A::getTime)
{
}
};
class CompareByValue : public CompareBy<double>
{
public:
CompareByValue( const A& a ) : CompareBy(a, &A::getValue)
{
}
};
struct byTime_compare {
bool operator() (const A& lhs, const A& rhs) const {
return CompareByTime(lhs) < CompareByTime(rhs);
}
};
int main()
{
A a, b;
...
if (CompareByValue(a) < CompareByValue(b))
{
...
}
std::set<A, byTime_compare> mySet;
}
short answer: don't
I explained why in a comment, the main reason is, it introduces ambiguity in your code and reduces readability which is the opposite of what operators are meant to do. Just use different methods and provide ways to pick which one to use for this sort (like comparers). While I was typing this, people posted good examples of that, even some using a bit of metaprogramming.
however, for science, you kinda can. While you can't add a parameter to an operator (a binary operator is a binary operator, and there doesn't seem to be a syntax to add this third argument somewhere) you can make your operator mean different things in different contexts (c++ context, for a line of code or for a block delimited by '{}')
here done very quickly using construction/destruction order (similar implementation to a trivial lock with no consideration for thread safety):
the comparison looks like:
Thing::thingSortingMode(Thing::thingSortingMode::alternateMode), Thing{1, 2} < Thing{3, 4};
run this example online: http://cpp.sh/3ggrq
#include <iostream>
struct Thing {
struct thingSortingMode {
enum mode {
defaultMode,
alternateMode
};
mode myLastMode;
thingSortingMode(mode aMode) { myLastMode = Thing::ourSortingMode; Thing::ourSortingMode = aMode; std::cout << "\nmode: " << aMode << "\n"; }
~thingSortingMode() { Thing::ourSortingMode = myLastMode; std::cout << "\nmode: " << myLastMode << "\n";}
};
bool operator < (Thing another) {
switch (ourSortingMode) //I use an enum, to make the example more accessible, you can use a functor instead if you want
{
case thingSortingMode::alternateMode:
return myValueB < another.myValueB;
break;
default:
return myValueA < another.myValueA;
break;
}
}
static thingSortingMode::mode ourSortingMode;
int myValueA;
int myValueB;
};
Thing::thingSortingMode::mode Thing::ourSortingMode = Thing::thingSortingMode::defaultMode;
int main()
{
Thing a{1, 1}, b{0, 2}; // b < a in default mode, a < b in alternate mode
std::cout << (a < b); //false
{
Thing::thingSortingMode ctx(Thing::thingSortingMode::alternateMode);
std::cout << (a < b); //true
Thing::thingSortingMode(Thing::thingSortingMode::defaultMode), std::cout << (a < b), //false
Thing::thingSortingMode(Thing::thingSortingMode::alternateMode), std::cout << (a < b); //true
std::cout << (a < b); //true
}
std::cout << (a < b); //false
}
Note that this construction/destruction trick can manage any kind of contextual state, here is a richer example with 4 states and more nested contexts
run this example online: http://cpp.sh/2x5rj
#include <iostream>
struct Thing {
struct thingSortingMode {
enum mode {
defaultMode = 1,
alternateMode,
mode3,
mode4,
};
mode myLastMode;
thingSortingMode(mode aMode) { myLastMode = Thing::ourSortingMode; Thing::ourSortingMode = aMode; std::cout << "\nmode: " << myLastMode << " -> " << aMode << "\n"; }
~thingSortingMode() { std::cout << "\nmode: " << Thing::ourSortingMode << " -> " << myLastMode << "\n"; Thing::ourSortingMode = myLastMode; }
};
static thingSortingMode::mode ourSortingMode;
};
Thing::thingSortingMode::mode Thing::ourSortingMode = Thing::thingSortingMode::defaultMode;
int main()
{
Thing::thingSortingMode ctx(Thing::thingSortingMode::mode3);
{
Thing::thingSortingMode ctx(Thing::thingSortingMode::alternateMode);
{
Thing::thingSortingMode ctx(Thing::thingSortingMode::mode4);
{
Thing::thingSortingMode ctx(Thing::thingSortingMode::defaultMode);
std::cout << "end sub 3 (mode 1)\n";
}
std::cout <<
(Thing::thingSortingMode(Thing::thingSortingMode::alternateMode), "this is the kind of things that might behave strangely\n") <<
(Thing::thingSortingMode(Thing::thingSortingMode::defaultMode), "here both are printed in mode 2, but it's a direct consequence of the order in which this expression is evaluated\n"); //note though that arguments are still constructed in the right state
std::cout << "end sub 2 (mode 4). Not that we still pop our states in the right order, even if we screwed up the previous line\n";
}
std::cout <<
(Thing::thingSortingMode(Thing::thingSortingMode::alternateMode), "this on the other hand (mode 2)\n"),
std::cout <<
(Thing::thingSortingMode(Thing::thingSortingMode::defaultMode), "works (mode 1)\n"); //but pay attention to the comma and in which order things are deleted
std::cout << "end sub 1 (mode 2)\n";
}
std::cout << "end main (mode 3)\n";
}
output:
mode: 1 -> 3
mode: 3 -> 2
mode: 2 -> 4
mode: 4 -> 1
end sub 3 (mode 1)
mode: 1 -> 4
mode: 4 -> 1
mode: 1 -> 2
this is the kind of things that might behave strangely
here both are printed in mode 2, but it's a direct consequence of the order in which this expression is evaluated
mode: 2 -> 1
mode: 1 -> 4
end sub 2 (mode 4). Not that we still pop our states in the right order, even if we screwed up the previous line
mode: 4 -> 2
mode: 2 -> 2
this on the other hand (mode 2)
mode: 2 -> 1
works (mode 1)
mode: 1 -> 2
mode: 2 -> 2
end sub 1 (mode 2)
mode: 2 -> 3
end main (mode 3)
mode: 3 -> 1
Another approach, very simple: add template comparator functions to the A class makes it easy to do a comparison in the end and is really error prone:
#include <iostream>
#include <set>
using namespace std;
class A
{
public:
int when;
double value;
int getTime() const { return when; }
double getValue() const { return value; }
template<typename T>
bool isLower( T (A::*getter)() const,
bool strict,
const A& right ) const
{
if ( strict )
return ((*this).*getter)() < (right.*getter)();
else
return ((*this).*getter)() <= (right.*getter)();
}
template<typename T>
bool isGreater( T (A::*getter)() const,
bool strict,
const A& right ) const
{
if ( strict )
return ((*this).*getter)() > (right.*getter)();
else
return ((*this).*getter)() >= (right.*getter)();
}
template<typename T>
bool isEqual( T (A::*getter)() const,
const A& right ) const
{
return ((*this).*getter)() == (right.*getter)();
}
};
struct byTime_compare {
bool operator() (const A& lhs, const A& rhs) const {
return lhs.isLower( &A::getTime, true, rhs );
}
};
int main()
{
A a, b;
if ( a.isLower( &A::getValue, true, b ) ) // means a < b by value
{
// ...
}
std::set<A, byTime_compare> mySet;
}
I am trying to compare different classes of objects in C++.
Everything works well if I remove section3. But I'd like to know how to edit the comparative operators == and != to make it work without any errors?
The error that I get is "no match for 'operator==' (operand types are 'Fruit' and 'Plant') "
Here is my code :
#include <iostream>
#include <string>
class Plant
{
public:
Plant(std::string name) : type_(name)
{ }
bool operator==(const Plant &that) const
{ return type_ == that.type_; }
bool operator!=(const Plant &that) const
{ return !operator==(that); }
void print()
{ std::cout << type_ << std::endl; }
protected:
std::string type_;
};
class Fruit: public Plant
{
public:
Fruit(std::string name, std::string taste)
: Plant(name)
, taste_(taste)
{ }
bool operator==(const Fruit& that) const
{
return ( (taste_ == that.taste_) && (Plant::operator==(that)) );
}
bool operator!=(const Fruit& that) const
{
return !operator==(that);
}
void print()
{
Plant::print();
std::cout << taste_ << std::endl;
}
private:
std::string taste_;
};
int main()
{
Plant a("Maple");
a.print();
Plant b("Maple");
if (a == b)
{
std::cout << "a and b are equal" << std::endl;
}
else
{
std::cout << "a and b are not equal" << std::endl;
}
Fruit c("Apple","sweet");
c.print();
Fruit d("Apple","sweet");
if (c == d)
{
std::cout << "c and d are equal" << std::endl;
}
else
{
std::cout << "c and d are not equal" << std::endl;
}
if (a == c)
{
std::cout << "a and c are equal" << std::endl;
}
else
{
std::cout << "a and c are not equal" << std::endl;
}
/* Section 3 */
if (c == a)
{ std::cout <<"c and a are equal\n"<< std::endl; }
else
{ std::cout <<"c and a are not equal\n"<< std::endl; }
if (a != c)
{ std::cout <<"c and a are not equal\n"<< std::endl; }
else
{ std::cout <<"c and a are equal\n"<< std::endl; }
return 0;
}
Thanks ..
You can add non-member functions,
bool operator==(Fruit const& f, Plant const& p)
{
return false;
}
bool operator!=(Fruit const& f, Plant const& p)
{
return !(f == p);
}
This will work for one sub-type of Plant. This approach is not scalable. If you create more sub-types of Plant, you'll need to use a different approach.
You have to either implement comparitor operators to compare Fruit and Plant or downcast the Fruit to a plant in the comparison:
bool operator==(const Plant& plant, const Fruit& fruit) { /* test here */ }
bool operator==(const Fruit& fruit, const Plant& plant) { return (plant == fruit); }
Or if you have pointers:
Fruit* fruit = new Fruit("apple", "sour");
Plant* plant = new Plant("maple");
if(*plant == *static_cast<Plant*>(fruit)) {}
It's bit unclear what you're trying to achieve, but apparently it involves dynamic type checking where two objects compare equal if they dynamically are of some common base type X and are equal according to some criterion specified in that base type.
Finding the common type X is in general a thorny problem, because C++ supports multiple inheritance. But if one assumes, for simplicity, single inheritance, that is, the case where each class has at most one base class, then one can let one of the objects that are involved in a comparison, walk up the base class chain and use e.g. dynamic_cast to check if the other object is of this type, e.g. like this:
#include <string>
#include <utility> // std::move
using Byte_string = std::string;
class Base
{
private:
Byte_string s_;
protected:
virtual
auto equals( Base const& other ) const
-> bool
{ return s_ == other.s_; }
public:
friend
auto operator==( Base const& a, Base const& b )
-> bool
{ return a.equals( b ); }
explicit Base( Byte_string s )
: s_( move( s ) )
{}
};
class Derived
: public Base
{
private:
Byte_string t_;
protected:
auto equals( Base const& other ) const
-> bool override
{
if( auto p_other = dynamic_cast<Derived const*>( &other ) )
{
return Base::equals( other ) and t_ == p_other->t_;
}
return Base::equals( other );
}
public:
Derived( Byte_string s, Byte_string t )
: Base( move( s ) )
, t_( move( t ) )
{}
};
class Most_derived
: public Derived
{
private:
int u_;
protected:
auto equals( Base const& other ) const
-> bool override
{
if( auto p_other = dynamic_cast<Most_derived const*>( &other ) )
{
return Derived::equals( other ) and u_ == p_other->u_;
}
return Derived::equals( other );
}
Most_derived( Byte_string s, Byte_string t, int u )
: Derived( move( s ), move( t ) )
, u_( u )
{}
};
#include <iostream>
using namespace std;
auto main() -> int
{
Base a( "Maple" );
Base b( "Maple" );
cout << "a and b are " << (a == b? "" : "not ") << "equal.\n";
Derived c( "Apple", "sweet" );
Derived d( "Apple", "sweet" );
cout << "c and d are " << (c == d? "" : "not ") << "equal.\n";
cout << "a and c are " << (a == c? "" : "not ") << "equal.\n";
cout << "c and a are " << (c == a? "" : "not ") << "equal.\n";
Base& x = d;
cout << "x and c are " << (x == c? "" : "not ") << "equal.\n";
cout << "c and x are " << (c == x? "" : "not ") << "equal.\n";
}