codablank1
codablank1

Reputation: 6175

C++ : calling the right method of a derived class according to the types of the arguments

Let say we have a base class and its two derived classes; The base class owns a method execute and each derived class implements a different version of this method with different types and number of arguments; I can't use a virtual method because signature should be then exactly the same for each derived class; My goal is to offer a base execute method which accepts any kind of arguments, deducts their types, and dispatch them to the right method in the right derived class; I took a look at the Visitor pattern, but I'm looking for a more flexible and elegant solution;

edit : I want to store those classes in a vector, so I need a base class

Here is my try (I don't know what to put in the body of base execute) under gcc 4.5:

class Base {

  public:

  Base();
  ~Base();

  template<typename ...Args>
  void execute(Args... arg)
  {
    //calls the right method
    //execute(int i) or execute(int i, float f)
    //as Args are int or int and float
  }

};

class DerivedA : public Base
{

  public:

  DerivedA();
  ~DerivedA();

  void execute(int i){ /*do something with i*/}

};

class DerivedB : public Base
{

  public:

  DerivedB();
  ~DerivedB();

  void execute(int i, float f){/*do something with i and f*/}

};

void test()
{
  Base* b1 = new DerivedA();
  Base* b2 = new DerivedB();

  int i = 5;
  b1->execute(i); //should call DerivedA.execute(int i)
  float f = 5.0f;
  b2->execute(i, f); //should call DerivedB.execute(int i, float f)

}

Upvotes: 6

Views: 408

Answers (4)

Tam&#225;s Szelei
Tam&#225;s Szelei

Reputation: 23961

Compile-time or runtime?

You need to know if you can decide at compile-time which method you want to call. If you want to decide at runtime, then that is called multiple dispatch and there is no built-in, short solution for it in C++ (see also the question Multiple dispatch in C++). You can sort of emulate it with the Visitor pattern or double dispatching. Here is a paper about implementing multimethod support for a C++ compiler by Bjarne Stroustroup and others.

Compile time implementation with a free function

If you know the type of your instances at compile time (i.e. you don't have to use Base* pointers), you can use a variadic template approach with static polymorphism (and you won't even need a common base class):

#include <iostream>

class DerivedA //: public Base
{
public:
    void execute(int i)
    { 
        std::cout << "I'm DerivedA::execute(int)! " << std::endl; 
    }
};

class DerivedB //: public Base
{
public:
    void execute(int i, float f) 
    {
        std::cout << "I'm DerivedB::execute(int, float)! " << std::endl; 
    }
};

template<typename Class, typename... Args>
void execInvoker(Class* obj, Args... args)
{
    static_cast<Class*>(obj)->execute(std::forward<Args>(args)...);
}

int main(int argc, char* argv[])
{
    DerivedA a;
    DerivedB b;

    int i = 5;
    float f = 5.2f;
    execInvoker(&a, i);
    execInvoker(&b, i, f);
}

You will get compilation errors if you try to invoke an execute method that doesn't exist (wrong types, or wrong number of arguments). I tested the above code with g++ 4.6 and the output is the expected:

$ g++ -std=c++0x -Wall variadic.cpp 
$ ./a.out 
I'm DerivedA::execute(int)! 
I'm DerivedB::execute(int, float)!

A similar approach without a free function

If you don't want to use a free function, you can use a template proxy class to save the type information.

template<typename Class>
class Proxy
{
private:
    Class* obj;

public:
    Proxy(Class* _obj) : obj(_obj) {}

    template<typename... Args>
    void execute(Args... args)
    {
        obj->execute(std::forward<Args>(args)...);
    }
};

This allows the following code:

Proxy<DerivedA> proxy(&a);
proxy.execute(i);

An obvious advantage of this approach is that you can pass this proxy object to template functions such as this one:

template<typename Class>
void proxyUser(Proxy<Class>& p)
{
    p.execute(4, 0.3f);
}

And it will call the correct execute. For specific cases, you can specialize this template function.

Upvotes: 1

sergio
sergio

Reputation: 69037

If I understand correctly what you are trying to accomplish, you could find it useful having a look at the "double dispatch" pattern:

double dispatch is a special form of multiple dispatch, and a mechanism that dispatches a function call to different concrete functions depending on the runtime types of two objects involved in the call (source)

Roughly: your client object calls "execute" on the target object:

target.execute(client);

the target object calls a method on an intermediate object that acts as an extended virtual table (actually, a multi-dispatch table):

dispatchTable.execute(client, *this);  //-- target calls this

and dispatch table at its turns calls the right method (with the complete signature) on the target object:

<get arguments from client>
target.specific_execute(arguments)

Alternatively, and possibly more conveniently, the dispatch table mechanism can be offered by the client object itself. So, target::execute calls:

client.execute(target);

and client::execute(target) will finally call:

target.specific_execute(args);

The client class will provide a set of overaloaded execute methods, each for a specific target type. The method will encapsulate the knowledge about the specifics of that object execute arguments.

This could require some refactoring of your design (in the first approach, client has to offer a way to retrieve the arguments to the call), and it possibly looks like quite a low-level implementation (a dispatch table), but it is a clean approach, IMO.

class Client;
struct Base {
    virtual void dispatch(Client& c);
    void execute(Base& b) {
        std::cout << "void execute(Base&)" << std::endl;
    }
};

struct DerivedA : public Base {
    void exec(int i){ 
        /*do something with i*/
        std::cout << "void execute(int i)" << std::endl;
    }
};

struct DerivedB : public Base {
    void exec(int i, float f)
    {
        std::cout << "void execute(int i, float f)" << std::endl;
    }
};

struct Client {
    int i;
    float f;

    void execute(Base& obj) {
    }
    void execute(DerivedA& obj) {
        obj.exec(i);
    }
    void execute(DerivedB& obj) {
        obj.exec(i, f);
    }
    void doTest() {
        Base* b1 = new DerivedA();
        Base* b2 = new DerivedB();
        b1->dispatch(*this);
        b2->dispatch(*this);
    }
};

void Base::dispatch(Client& c) {
    c.execute(*this);
}
void DerivedA::dispatch(Client& c) {
    c.execute(*this);
}
void DerivedB::dispatch(Client& c) {
    c.execute(*this);
}

int main (int argc, char * const argv[]) {
    // insert code here...
    std::cout << "Hello, World!\n";

    Client c;
    c.doTest();

    return 0;
}

Upvotes: 0

celtschk
celtschk

Reputation: 19731

The following uses an intermediate class in between the base and the derived class:

#include <utility>
#include <iostream>
#include <stdexcept>

template<typename... Args> class Intermediate;

class Base
{
public:
  virtual ~Base() {}

  template<typename ...Args>
  void execute(Args... args)
  {
    typedef Intermediate<Args...>* pim;
    if (pim p = dynamic_cast<pim>(this))
    {
      p->execute(std::forward<Args>(args)...);
    }
    else
    {
      throw std::runtime_error("no suitable derived class");
    }
  }
};

template<typename... Args> class Intermediate:
  public Base
{
public:
  virtual void execute(Args ... arg) = 0;
};

class DerivedA:
  public Intermediate<int>
{
public:
  void execute(int i)
  {
    std::cout << "DerivedA: i = " << i << "\n";
  }
};

class DerivedB:
  public Intermediate<int, float>
{
public:
  void execute(int i, float f)
  {
    std::cout << "DerivedB: i = " << i << ", f = " << f << "\n";
  }
};

int main()
{
  Base* b1 = new DerivedA();
  Base* b2 = new DerivedB();

  int i = 5;
  b1->execute(i); //should call DerivedA.execute(int i)
  float f = 5.0f;
  b2->execute(i, f); //should call DerivedB.execute(int i, float f)
}

Upvotes: 5

Kerrek SB
Kerrek SB

Reputation: 477434

You cannot have an arbitray (= unbounded) number of virtual functions in the base class. You have to decide which functions should be available and declare those. Otherwise you don't need virtual functions, and you could just make some compile-time dispatch, perhaps simply through overload resolution like this:

struct Base
{
   void foo(int a)          { dynamic_cast<DerA*>(this)->fooimpl(a); }
   void foo(int a, float b) { dynamic_cast<DerB*>(this)->fooimpl(a, b); }
   void foo(bool a, char b) { dynamic_cast<DerC*>(this)->fooimpl(a, b); }

   virtual ~Base() { }  // dynamic cast requires polymorphic class
};

You should add a check for validity, of course:

if (DerA * p = dynamic_cast<DerA*>(this)) { p->fooimpl(a)); }

Upvotes: 1

Related Questions