0

my assignment in school requires me to build an abstract class for virtual operator overloads, and then a class that makes it work. Specifically: IComparable - the abstract

    class IComparable{
public:
    virtual bool operator== (const IComparable&) const = 0;
    virtual bool operator< (const IComparable&) const = 0;
    virtual bool operator> (const IComparable&) const = 0;
    virtual bool operator!= (const IComparable&) const = 0;
    virtual bool operator<= (const IComparable&) const = 0;
    virtual bool operator>= (const IComparable&) const = 0;
};

and Date - the actual class

class Date : public IPrintable, IComparable {
private:
    int day;
    int month;
    int year;
public:
    Date(int, int, int);
    void setDay(int);
    void setMonth(int);
    void setYear(int);
    bool Comparison(const Date&, const Date&) const;
    bool larger(const Date&, const Date&) const;
    bool operator== (const IComparable& other)const override;
    bool operator< (const IComparable& other)const override; 
    bool operator> (const IComparable& other)const override;
    bool operator!= (const IComparable& other)const override;
    bool operator<= (const IComparable& other)const override;
    bool operator>= (const IComparable& other)const override;

    ~Date();
};

The problem appears when i try and implement it in Date.cpp

I dont really know how to.

I thought i was supposed to use dynamic_cast to downcast a IComparable into Date to use in functions. however i run into problems with that. I Tried a few different implementations and im gonna toss them here, just in case one of them was somewhere close to what i have to do.

bool Date::operator< (const IComparable& other)const override  {

    Date *D1 = dynamic_cast<Date*>(other);

    return(larger(*this,*D1);
}


bool Date::operator> (const IComparable& other)const override{
    return(larger(other, *this));
}

bool Date::operator!= (const IComparable& other)const {
    bool Flip;
    Flip = Comparison(*this, other);
    return(!Flip);
}

Do i need to type override? Because it shows up as an error "expected a {"

And overall, what am i doing wrong. Thanks in advance.

Merkava
  • 5
  • 4
  • In the first varialnt you are trying to cast a reference to a pointer. This dowmesn't normally work. Pick one thing (most probably a reference). The other two are just shifting the dirt. At some place you need to cast. – n. m. could be an AI Jan 20 '20 at 10:21
  • How do i send it over then? "larger" func checks if the right is bigger than the left, so it needs to be able to see the "other" private members as a Date class. How do i downcast it then? – Merkava Jan 20 '20 at 10:36
  • Aside: the fact that you have to `dynamic_cast` shows that this school assignment is a bad idea. Consider `class Address : IComparable`, does it make sense ask if a `Date` is less than an `Address`? – Caleth Jan 20 '20 at 10:40
  • As in comparing different classes? No, it doesnt sound right. And yeah, the assignments in this course never were really good. Very convoluted, had multiple programmers say that no one should write stuff that way if they want to get a job. However, i must use IComparable.h and Date,h and cpp for this. Theres more to the assignment but thats irrelevant right now – Merkava Jan 20 '20 at 10:46
  • *"my assignment in school requires me to build an abstract class for virtual operator overloads"* Seems not a good way in C++. – Jarod42 Jan 20 '20 at 10:59
  • Two other questions with apparently the same assignment and problems: https://stackoverflow.com/questions/59601214/c-add-template-to-an-existing-class and https://stackoverflow.com/questions/59717270/need-help-figuring-out-how-to-implement-an-interface-in-c If these three are all different students, I think this is a good indication that there is some problem with the assignment. – walnut Jan 20 '20 at 11:07
  • The first one doesnt load, but the second link is that exact assignment. So i guess another classmate. – Merkava Jan 20 '20 at 11:13
  • @Merkava The other one was closed and deleted. Only 10k+ members can see it now, but it has the exact same definition for `IComparable` in it. – walnut Jan 20 '20 at 11:15
  • What uni is that? Just curious. You don't have to answer. – n. m. could be an AI Jan 20 '20 at 12:01
  • Hopefully, at some point in the future, schools will start teaching `concept`s as interfaces. Point your instructor to [std::totally_ordered](https://en.cppreference.com/w/cpp/concepts/totally_ordered) – Caleth Jan 20 '20 at 12:10
  • Your interface is written exceptionally badly even for the lax higher education standards. Nirnally one would have a single pure virtual Comparison function returning something that is **not** a bool, and all the rest implemented in terms of it **non-virtually**. – n. m. could be an AI Jan 20 '20 at 12:34
  • It may be written bad, i dont disagree, however the assignment states that the interface must include the relational operators, and then lists those 6. – Merkava Jan 20 '20 at 12:48
  • If it only lists them and doesn't specify that they should all be pure virtual, then I would propose to implement them all in terms of a single low level function. Think about what type it might return. Then you only need to override a single function in all derived classes. – n. m. could be an AI Jan 20 '20 at 12:58
  • Sadly, it states that all of them have to be pure virtual. I can see myself how this is not a smart way to do things. – Merkava Jan 20 '20 at 13:06
  • Also worth noting, that most compilers will simply strip out the base class anyway. A process known as "Devirtualisation" https://marcofoco.com/the-power-of-devirtualization/ – Tiger4Hire Jan 21 '20 at 09:59

5 Answers5

1

You can never compare a concrete thing to an abstract thing. It's like asking "Which is more expensive this book or reading material in general?".
To solve this you need to make both arguments concrete.

The normal way to do this is using a pattern known as double dispatch.
Take this line...

bool operator== (const IComparable& other)const override;

How could you possibly implement this function? Think about it. The function knows that "this" is a Date, but what is other? It could be anything derived from IComparable. The only thing IComparable declares about itself is that is comparable.

In double dispatch, you use the fact that every function must know it's own type. It then calls a concrete compare method on the second argument, passing it self as the argument. This function then knows the type of both arguments and may solve the problem.
This is shown on the wikipedia page above under "Double dispatch in C++".

Tiger4Hire
  • 1,065
  • 5
  • 11
  • 1
    DD is probably an overkill here. It is good if you have N types and O(N*N) operations (think arithmetic types). But here you are not going to compare a Date to a Chevrolet. – n. m. could be an AI Jan 20 '20 at 12:28
0
return(larger(*this,*D1);

That's a fairly obvious syntax error - mismatched (. Fix this before anything else.

To get a pointer from the dynamic_cast operator, we need to pass a pointer in (using the address-of operator, &). Also, there's little point using dynamic_cast and not checking the validity of the result - we'll get nullptr if other isn't a Date.

bool Date::operator< (const IComparable& other) const
{
    auto *other_date = dynamic_cast<const Date*>(&other);
    if (!other_date) { return false; }

    return larger(*this,*other_date);
}

Alternatively, we stick with references, but catch std::bad_cast exception instead of testing for null:

bool Date::operator< (const IComparable& other) const
{
    try
        auto &other_date = dynamic_cast<const Date&>(&other);
        return larger(*this,other_date);
    } catch (std::bad_cast&) {
        return false;
    }
}

Note that either version is still quite poor, as we don't provide a good total ordering of all IComparable objects here - that's the main reason that such interfaces are a bad idea in C++ (perhaps that is intended to be the lesson of the teaching here?).

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
0

You are almost correct with your first attempt

bool Date::operator< (const IComparable& other) const { // no override in out-of-line definition

    const Date *D1 = dynamic_cast<const Date*>(&other); // cast pointer to other, retaining constness

    // throw if D1 == nullptr?

    return larger(*this,*D1);
}

Alternatively, you can

bool Date::operator< (const IComparable& other) const { // no override in out-of-line definition

    const Date& D1 = dynamic_cast<const Date&>(other); // will throw if other isn't a Date

    return larger(*this, D1);
}

Note that it's useful to implement only one of <, <=, > and >= like that. The remaining can be implemented in terms of that one. This makes it easier not to make a mistake.

bool Date::operator<= (const IComparable& other) const {
    return !(other < *this);
}

bool Date::operator> (const IComparable& other) const {
    return other < *this;
}

bool Date::operator>= (const IComparable& other) const {
    return !(*this < other);
}

You can also implement == and != in terms of <, although if you have a shortcut for inequality it can be preferable to implement == on it's own.

bool Date::operator== (const IComparable& other) const {
    return !(*this < other) && !(other < *this);
}

bool Date::operator!= (const IComparable& other) const {
    return !(*this == other);
}
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • This is really good, thank you. Only thing to add is D1 has to be a const, as well as the type that its cast to, After inserting those changes, everything seems to fall into place. Also thanks for the tip of just using that operator. I simply used the function of larger and changed stuff in the way its called in returned, but your idea is cleaner. – Merkava Jan 20 '20 at 12:54
  • @Merkava probably not applicable to your assignment, but you can use this pattern to only ever implement `>` et. al. once, as a template (or zero times, using [an existing template](https://www.boost.org/doc/libs/1_72_0/libs/utility/operators.htm)) – Caleth Jan 20 '20 at 13:00
  • A reference dynamic_cast already throws if there is a type mismatch. There's no need to reinvent the wheel. – n. m. could be an AI Jan 20 '20 at 13:01
  • @n.'pronouns'm. that depends whether you want to throw `std::bad_cast` or `::bad_compare` – Caleth Jan 20 '20 at 13:03
  • @Caleth fair point, but I think in this academic setting you don't really care. – n. m. could be an AI Jan 20 '20 at 14:18
  • You can use rel_ops (https://en.cppreference.com/w/cpp/utility/rel_ops/operator_cmp) to use the standard definition of the other comparitors. – Tiger4Hire Jan 21 '20 at 09:48
  • I'm not sure this code is particularly useful, as it only works for comparing a Date with another Date. This is just a complex (and bug-prone) and slow way of defining a normal member function. – Tiger4Hire Jan 21 '20 at 09:50
0

Ive found another way to deal with this problem, which makes a lot of sense. We turn IComparable into a template class like this:

template<typename T>
class IComparable{
public:
    virtual bool operator== (const T&) const = 0;
    virtual bool operator< (const T&) const = 0;
    virtual bool operator> (const T&) const = 0;
    virtual bool operator!= (const T&) const = 0;
    virtual bool operator<= (const T&) const = 0;
    virtual bool operator>= (const T&) const = 0;
};

And then the Date is declared with IComparable that way, we keep the Comparable filled with pure virtuals, and dont suffer from the whole dynamic_cast problem

class Date : public IPrintable<Date>, IComparable<Date> {
.
.
.
bool larger(const Date&, const Date&) const;
    bool operator== (const Date &other)const;
    bool operator< (const Date &other)const;
.
.
Merkava
  • 5
  • 4
  • That defeats the whole point of `virtual`. You could just as well remove the `IComparable` and `IPrintable` base classes here without change in behavior. – walnut Jan 21 '20 at 12:15
-2

from cppreference.com

dynamic_cast < new-type > ( expression )

If the cast is successful, dynamic_cast returns a value of type new-type. If the cast fails and new-type is a pointer type, it returns a null pointer of that type. If the cast fails and new-type is a reference type, it throws an exception that matches a handler of type std::bad_cast.