Home > Software design >  C : Function call precedence rules for calling functions on objects of derived classes?
C : Function call precedence rules for calling functions on objects of derived classes?

Time:09-05

I have a class Iterator, for which I have defined various operator overloads for equality testing (<, <=, >, >=, == and !=) as friend functions. Prototype example:

friend bool operator<(const Iterator &A, const Iterator &B);

class RevIterator inherits (virtually) from Iterator. Naturally, I have had to overload the operators <, <=, > and >= for this class (as friend functions again). Prototype example:

friend bool operator<(const RevIterator &A, const RevIterator &B);

I also have a class ConstIterator which inherits (virtually) from Iterator, for which I have not defined more operator overloads (they are meant to behave in the same way as for Iterator).

Finally, I have a class ConstRevIterator which inherits from RevIterator and ConstIterator, for which I have not defined any operator overloads.

Visual class hierarchy:

class Iterator {};
class RevIterator : virtual public Iterator {};
class ConstIterator : virtual public Iterator {};
class ConstRevIterator : public RevIterator, public ConstIterator {};

My assumption was that for the operator <, for example, operating on two ConstRevIterators, the function available for one of the parent classes, RevIterator (2nd func. prototype above) would be called, instead of the function for the grandparent class, Iterator (1st func. prototype above).

In summary, what are the rules for which function gets called on a reference to a 'grandchild' object, when there are functions available for its parent and grandparent classes?

CodePudding user response:

Your assumption seems correct. You may find rules of overload resolution here https://en.cppreference.com/w/cpp/language/overload_resolution

Here is the part concerning your question:

  1. If Mid is derived (directly or indirectly) from Base, and Derived is derived (directly or indirectly) from Mid

a) Derived* to Mid* is better than Derived* to Base*

b) Derived to Mid& or Mid&& is better than Derived to Base& or Base&&

c) Base::* to Mid::* is better than Base::* to Derived::*

d) Derived to Mid is better than Derived to Base

e) Mid* to Base* is better than Derived* to Base*

f) Mid to Base& or Base&& is better than Derived to Base& or Base&&

g) Mid::* to Derived::* is better than Base::* to Derived::*

h) Mid to Base is better than Derived to Base

I also produced some code to verify it: https://godbolt.org/z/bc47dzxvY

CodePudding user response:

In summary, what are the rules for which function gets called on a reference to a 'grandchild' object, when there are functions available for its parent and grandparent classes?

The answer here depends on what you mean by a "reference to a 'grandchild' object".

These will be statically bound, so if you have something like:

void foo(Iterator &a, Iterator &b) {
    if (a != b) // ...
}

...it'll use bool operator!=(const Iterator &, const Iterator &) to do the comparison, even if the iterator you pass is a ConstIterator, RevIterator or ConstRevIterator.

On the other hand, if you have a function something like:

void bar(ConstRevIterator &a, ConstRevIterator &b) { 
    if (a != b) // ...
}

Now, since you've directly specified that it's a reference to a ConstRevItertor, it'll use bool operator!=(const ConstRevIterator &, const ConstRevIterator &) to do the comparison.

If you want virtual behavior, so the behavior depends on the actual type passed rather than the type declared for the parameter, you'll typically something more like this:

class Iterator {
protected:
    virtual bool isEq(Iterator const &other) const { 
      // implement test for equality
    }

    virtual bool isNeq(Iterator const &other) {
        return !(isEq(other));
    }

    // and so on for the other comparisons

    friend bool operator==(Iterator const &a, Iterator const &b) { 
        return a.operator==(b);
    }

    // and likewise for the other comparisons

};

class ConstIterator : public Iterator {
protected:
     bool isEq(ConstIterator const &other) const override {
         // ...
     }

     bool isNeq(ConstIterator const &other) const override {
         // ..
     }
     // and so on
};

In this case, you define virtual member functions to do the real comparisons, friend functions that take references to the base class, and invoke the virtual member functions do to the real comparison, so you'll get virtual dispatch, and the comparison will be done for the most derived type.

This has a bit of a problem though: all the functions get virtual dispatch, which can be on the slow side, especially for something as simple as comparing two iterators--virtual dispatch could easily take longer than the comparison itself.

  • Related