user19018
user19018

Reputation: 2499

Virtual overloading of the comparison operator

Suppose we have the following snippet:

class A
{
public:
    virtual bool operator< (const A &rhs) const;
};

class B: public A;

class C: public A;

I want the comparison to depend on the real types of both the left and right hand side, for example:

x < y == true    if type(x) == B and type(y) == C
x < y == false   if type(x) == C and type(y) == B

The situation could be more complex, with much more derived classes than two. Of course, operator< has to be a virtual function. Is there an elegant way to write this?

Upvotes: 0

Views: 594

Answers (2)

Richard Hodges
Richard Hodges

Reputation: 69892

///
/// goal:       provide partial ordering of objects derived from A on the basis
///             only of class type.
/// constraint: ordering specified by us
///

#include <vector>
#include <typeindex>
#include <algorithm>
#include <iostream>


class A
{
public:
    virtual bool operator< (const A &rhs) const = 0;

    static const std::vector<std::type_index>& ordering();
};

template<class T> struct impl_A : public A
{
    bool operator< (const A &rhs) const override
    {
        auto& o = ordering();
        auto first = std::begin(o);
        auto last = std::end(o);

        auto il = std::find(first, last, typeid(T));
        auto ir = std::find(first, last, typeid(rhs));
        return il < ir;
    }
};

class B: public impl_A<B> {};

class C: public impl_A<C> {};

const std::vector<std::type_index>& A::ordering()
{
    // specify fording of types explicitly
    static const std::vector<std::type_index> _ordering { typeid(B), typeid(C) };
    return _ordering;
}



void test(const A& l, const A& r)
{
    if (l < r) {
        std::cout << typeid(l).name() << " is less than " << typeid(r).name() << std::endl;
    }
    else {
        std::cout << typeid(l).name() << " is not less than " << typeid(r).name() << std::endl;
    }
}

int main()
{
    test(B(), C());
    test(B(), B());
    test(C(), B());
    test(C(), C());

}

example output (clang):

1B is less than 1C
1B is not less than 1B
1C is not less than 1B
1C is not less than 1C

Fine! But (I was not precise enough in my question), when x and y share the same type (for example B), the result of x < y is given by a specific function const operator< (B &rhs) const in class ̀B. It is not necessarily false`.

OK, so we are revising requirements. This is a normal dialogue between users (who rarely realise the level of detail required in specifications) and the developers (who do!)

So this time we will say that any two dissimilar derived classes will have a consistent partial ordering (i.e. they will never compare equal and one will always compare less than the other) but we'll let the standard library decide which one comes first.

However, when the two classes being compared are of the same type, we would like to actually compare their values to determine ordering (and equivalence).

It would go something like this:

#include <vector>
#include <typeinfo>
#include <algorithm>
#include <iostream>
#include <tuple>
#include <iomanip>


class A
{
public:
    virtual bool operator< (const A &rhs) const = 0;

    std::ostream& print(std::ostream& os) const {
        handle_print(os);
        return os;
    }

private:
    virtual void handle_print(std::ostream&) const = 0;
};

std::ostream& operator<<(std::ostream& os, const A& a) {
    return a.print(os);
}

template<class T> struct impl_A : public A
{
    bool operator< (const A &rhs) const override
    {
        auto& rhs_info = typeid(rhs);
        auto& lhs_info = typeid(T);
        if (rhs_info == lhs_info) {
            // same type, so do comparison
            return static_cast<const T&>(*this).ordering_tuple() < static_cast<const T&>(rhs).ordering_tuple();
        }
        else {
            return lhs_info.before(rhs_info);
        }
    }
};

class B: public impl_A<B> {
public:
    B(int v) : _value(v) {}

    auto ordering_tuple() const {
        return std::tie(_value);
    }

private:

    void handle_print(std::ostream& os) const override {
        os << _value;
    }

    int _value;
};



class C: public impl_A<C> {
public:
    C(std::string v) : _value(std::move(v)) {}

    auto ordering_tuple() const {
        return std::tie(_value);
    }

private:

    void handle_print(std::ostream& os) const override {
        os << std::quoted(_value);
    }

    std::string _value;
};

// now we need to write some compare functions


void test(const A& l, const A& r)
{
    if (l < r) {
        std::cout << l << " is less than " << r << std::endl;
    }
    else {
        std::cout << l << " is not less than " << r << std::endl;
    }
}

int main()
{
    test(B(1), C("hello"));
    test(B(0), B(1));
    test(B(1), B(0));
    test(B(0), B(0));
    test(C("hello"), B(1));
    test(C("goodbye"), C("hello"));
    test(C("goodbye"), C("goodbye"));
    test(C("hello"), C("goodbye"));

}

example results:

1 is less than "hello"
0 is less than 1
1 is not less than 0
0 is not less than 0
"hello" is not less than 1
"goodbye" is less than "hello"
"goodbye" is not less than "goodbye"
"hello" is not less than "goodbye"

Upvotes: 2

Some programmer dude
Some programmer dude

Reputation: 409206

The only solution I see is to not have the operator< function as a virtual member function, but as a set of overloaded non-member functions: One "default" function which takes two references to A as arguments, and then one overload each for the special cases.

Upvotes: 2

Related Questions