Reputation:
I have the need for the following type of dispatch function. In my application, what I really have is a pointer to a state base class (foo) as well as a pointer to a measurement base class (bar). Based on what derived instance is passed to the dispatch2 function, I need to produce an estimated measurement given the state. For instance, the derived state class can be position and the derived measurement class can be ToF (time of flight). The handler would then take the state information from f (e.g. emitter location) as well as the collector information from b (e.g. sensor positions), and calculate the expect ToF given those. This is then returned and can be compared against the actual measurement value (b).
string dispatch2(foo* f, bar* b) {
if ( dynamic_cast<Foo>(f) ) return foo1(f,b);
else if ( dynamic_cast<FOo>(f) ) return foo2(f,b);
else if ( dynamic_cast<FOO>(f) ) return foo3(f,b);
throw std::runtime_error("dispatch for f not defined");
}
string foo1(foo* f, bar* b) {
if ( dynamic_cast<Bar>(b) ) return foo1bar1handler(f,b);
else if ( dynamic_cast<BAR>(b) ) return foo1bar2handler(f,b);
throw std::runtime_error("foo1: dispatch for b not defined");
}
string foo2(foo* f, bar* b) {
if ( dynamic_cast<Bar>(b) ) return foo2bar1handler(f,b);
else if ( dynamic_cast<BAR>(b) ) return foo2bar2handler(f,b);
throw std::runtime_error("foo2: dispatch for b not defined");
}
string foo3(foo* f, bar* b) {
if ( dynamic_cast<Bar>(b) ) return foo3bar1handler(f,b);
else if ( dynamic_cast<BAR>(b) ) return foo3bar2handler(f,b);
throw std::runtime_error("foo3: dispatch for b not defined");
}
string foo1bar1handler(foo* f, bar* b) {return "FooBar";}
string foo2bar2handler(foo* f, bar* b) {return "FooBAR";}
string foo3bar1handler(foo* f, bar* b) {return "FOoBar";}
string foo2bar2handler(foo* f, bar* b) {return "FOoBAR";}
string foo2bar1handler(foo* f, bar* b) {return "FOOBar";}
string foo2bar2handler(foo* f, bar* b) {return "FOOBAR";}
Obviously, there is no way to get around the need for defining the end methods for each combination I'd like to explicitly handle. But, I'm looking for alternative ways to implement this. Ideally, some pattern that would allow users to register each handler explicitly, and any combination that isn't handled could throw a runtime exception. Any suggestions would be appreciated. Thanks
Upvotes: 1
Views: 489
Reputation: 6131
One way (certainly not the only way) is to call a virtual function on foo, passing it bar. Each derived type of foo implements this dispatch function the same way, which will pass itself to a virtual handler function in bar. When you need to add more, you extend the interfaces to accept the new type(s). All the foo functions have the same implementation but they differ such that "this" is correctly the dynamic type of the object.
Also, Andrei Alexandrescu had a nice investigation into this with design alternatives in his (now not-so-modern) book Modern C++ Design, which still covers the idea but is written for c++98, but definitely still worth reading (though many things it says can't be done are now part of C++, partly due to that book.)
See it live: https://godbolt.org/z/oRyVJx
This example has 3 Foo classes, and 2 Bar classes.
#include <iostream>
class BarBase;
class FooBase {
public:
virtual ~FooBase() = default;
virtual void dispatch(BarBase*) = 0;
};
class Foo1;
class Foo2;
class Foo3;
class BarBase {
public:
virtual ~BarBase() = default;
virtual void accept(Foo1*) = 0;
virtual void accept(Foo2*) = 0;
virtual void accept(Foo3*) = 0;
};
class Bar1 : public BarBase {
public:
void accept(Foo1*) override;
void accept(Foo2*) override;
void accept(Foo3*) override;
};
class Bar2 : public BarBase {
public:
void accept(Foo1*) override;
void accept(Foo2*) override;
void accept(Foo3*) override;
};
class Foo1 : public FooBase {
public:
void dispatch(BarBase* bar) override { bar->accept(this); }
};
class Foo2 : public FooBase {
public:
void dispatch(BarBase* bar) override { bar->accept(this); }
};
class Foo3 : public FooBase {
public:
void dispatch(BarBase* bar) override { bar->accept(this); }
};
void Bar1::accept(Foo1 * f) { std::cout << "Bar1 accepting Foo1\n"; }
void Bar1::accept(Foo2 * f) { std::cout << "Bar1 accepting Foo2\n"; }
void Bar1::accept(Foo3 * f) { std::cout << "Bar1 accepting Foo3\n"; }
void Bar2::accept(Foo1 * f) { std::cout << "Bar2 accepting Foo1\n"; }
void Bar2::accept(Foo2 * f) { std::cout << "Bar2 accepting Foo2\n"; }
void Bar2::accept(Foo3 * f) { std::cout << "Bar2 accepting Foo3\n"; }
//
// Doesn't know which types of foo and bar it has, but it doesn't matter...
//
void call(FooBase& foo, BarBase& bar) {
foo.dispatch(&bar);
}
int main() {
Foo1 f1;
Foo2 f2;
Foo3 f3;
Bar1 b1;
Bar2 b2;
call(f1, b1);
call(f2, b1);
call(f3, b1);
call(f1, b2);
call(f2, b2);
call(f3, b2);
}
output:
Bar1 accepting Foo1
Bar1 accepting Foo2
Bar1 accepting Foo3
Bar2 accepting Foo1
Bar2 accepting Foo2
Bar2 accepting Foo3
Upvotes: 0