Stealthy Dirtbag
Stealthy Dirtbag

Reputation: 48

Changing behaviour of parent virtual function based on inherited type parameter

I have an inheritance hierarchy with a base class and two derived classes.

In the base class, I have a virtual function:

virtual void interact(const Base &other) const;

In the derived classes, they both have the following two functions, which are necessary as the two derived classes interact different with each other (i.e. Derived1 and Derived1 interact differently than Derived1 and Derived2) :

void interact(const Derived1 &other) const;
void interact(const Derived2 &other) const;

I want to be able to take two objects from a vector<Base> and call the virtual Base class method (e.g. base1.interact(base2)) and have the correct function in the derived classes to be called based on the derived types.

Is there a way I can do this, or is my approach wrong entirely?

Upvotes: 3

Views: 84

Answers (1)

skypjack
skypjack

Reputation: 50550

Is there a way I can do this, or is my approach wrong entirely?

The technique you are looking for is called double dispatching.
It is not wrong entirely. As an example, the pattern visitor is built on top of the same concept.

It follows a minimal, working example based on the details in your question:

#include<iostream>

struct Derived1;
struct Derived2;

struct Base {
    virtual void interact(const Base &) const = 0;
    virtual void interact(const Derived1 &) const = 0;
    virtual void interact(const Derived2 &) const = 0;
};

struct Derived1: Base {
    void interact(const Base &other) const override {
        other.interact(*this);
    }

    void interact(const Derived1 &) const {
        std::cout << "Derived1/Derived1" << std::endl;
    }

    void interact(const Derived2 &) const {
        std::cout << "Derived2/Derived1" << std::endl;
    }
};

struct Derived2: Base {
    void interact(const Base &other) const override {
        other.interact(*this);
    }

    void interact(const Derived1 &) const {
        std::cout << "Derived1/Derived2" << std::endl;
    }

    void interact(const Derived2 &) const {
        std::cout << "Derived2/Derived2" << std::endl;
    }
};

void foo(const Base &lhs, const Base &rhs) {
    lhs.interact(rhs);
}

int main() {
    foo(Derived1{}, Derived1{});
    foo(Derived1{}, Derived2{});
    foo(Derived2{}, Derived1{});
    foo(Derived2{}, Derived2{});
}

It requires all the interact methods to be virtual and part of the base class.
The basic idea is that the class to which a reference to a Base is passed as a parameter promotes and uses itself as a (let me say) tag to correctly dispatch the request to the right method.
It does that by calling back the invoker and passing itself as a function parameter.

Upvotes: 3

Related Questions