user1493321
user1493321

Reputation:

C++ "Virtual arguments"

Consider the following code:

class A {
public:
    virtual ~A() {}
};

class AA : public A {
};

////////////////////////////////////////

class B {
public:
    virtual void f(const A &a) {
        // code for A
    }
};

class BB : public B {
public:
    virtual void f(const AA &a) {
        // code for AA
    }
};

////////////////////////////////////////

int main() {
    A *a = new AA;
    B *b = new BB;

    b->f(*a);
}

Obviously, the vtables are constructed such that when the above is run, // code for A is executed. I am looking for a way to be able to execute instead // code for AA.

The motivation is that this is for a library of code where the end-user will often have to write classes of the form BB, and I would like the process to be as easy as possible (i.e. the user should not have to use RTTI to figure out what derived class of A they are dealing with). Any ideas (and voodoo from any version of the C++ standard) are appreciated.

Upvotes: 3

Views: 207

Answers (4)

Ryan Haining
Ryan Haining

Reputation: 36802

This is horrible style but it seems you are under some tight constraints

#include <typeinfo>

 // everything else unmodified 

class BB : public B {
public:
    virtual void f(const A& arg) override {
        try { //try to convert to an AA&
            const AA& a{dynamic_cast<const AA&>(arg)};
            // code for AA
        } catch (std::bad_cast) { // if it fails, pass it up to B::f as an A&
            this->B::f(arg);
        }
    }
};

////////////////////////////////////////

int main() {
    A *aa = new AA;
    A *a = new A;
    B *b = new BB;

    b->f(*a);  // code for A executed
    b->f(*aa); // code for AA executed
}

as per Alan Stoke's comment, dynamic casting pointers is much faster when it fails, so you could alternatively use this if you expect failure regularly:

class BB : public B {
public:
    virtual void f(const A& arg) override {
        const AA* ap = dynamic_cast<const AA*>(&arg);
        if (ap == nullptr) {
            return this->B::f(arg);
        }
        const AA& a{*ap}; // get a reference, if you want to
        // code for AA
    }
};

Upvotes: 3

Deduplicator
Deduplicator

Reputation: 45654

You can use RTTI and explicitly doing your own dispatching for that.

Co-Variant types only work for return types unfortunately.

Example:

class B {
    void f_base(const A &a) {
        // code for A
    }
public:
    virtual void f(const A &a) {
        f_base(a); // Moved the base case outside
                   // to isolate the dispatching mechanism.
    }
};

class BB : public B {
public:
    virtual void f(const A& a) {
        const AA* pAA = dynamic_cast<const AA*>(&a);
        if(pAA) {
            f(*pAA);
            return;
        }
        f_base(a);
    }
    void f(const AA &a) {
        // code for AA
    }
};

More discussion of this type of dispatch as well of nicer packaging with templates is demonstrated here: "type-switch" construct in C++11

Upvotes: 5

Aasmund Eldhuset
Aasmund Eldhuset

Reputation: 37950

You need something that resembles the mechanism that is known as double dispatch, which in C++ can only be implemented indirectly:

class A {
public:
    virtual callF(B & b) {
        b.f(this);
    }

    virtual callF(BB & b) {
        b.f(this);
    }
};

class AA : public A {
public:
    virtual callF(B & b) {
        b.f(this);
    }

    virtual callF(BB & b) {
        b.f(this);
    }
};

Upvotes: 3

Jonas B&#246;tel
Jonas B&#246;tel

Reputation: 4482

The concept you are looking for is called double dispatch. Read more about it on http://en.wikipedia.org/wiki/Double_dispatch

It is not built into C++ but there are various ways to emulate it. One of them being the visitor pattern also found in the above link.

However you'll probably find all approaches lacking of elegance because you need to introduce AA to B, BB to A, or use RTTI and casts.

Upvotes: 1

Related Questions