Can someone think of a good way to implement multiple dispatch with something like Object::foo overloads below?
class A { public: virtual void accept (Visitor&) = 0; }; class B : public A { virtual void accept (Visitor&) override; }; class C : public A { virtual void accept (Visitor&) override; }; class D : public A { virtual void accept (Visitor&) override; }; class Object { public: virtual double foo (A*, A*) { std::cout << "Object::foo A,A\n"; return 3.14; } virtual double foo (B*, B*) { std::cout << "Object::foo B,B\n"; return 3.14; } virtual double foo (B*, C*) { std::cout << "Object::foo B,C\n"; return 3.14; } virtual double foo (C*, B*) { std::cout << "Object::foo C,B\n"; return 3.14; } virtual double foo (C*, C*) { std::cout << "Object::foo C,C\n"; return 3.14; } virtual char foo (A*, A*, A*) const { std::cout << "Object::foo A,A,A\n"; return '&'; } virtual char foo (C*, B*, D*) const { std::cout << "Object::foo C,B,D\n"; return '!'; }
The client code that I execute to do multiple sending is as follows:
int main() { A* a[] = {new B, new C, new D}; Object* object = new Object; double d = object->foo (a[0], a[1]);
From my design, you can conclude that my solution fails in the service category. Each time a new function needs to be transferred with overloads, a new corresponding Visitor class must be written, etc. (For example, in this case ObjectFooVisitor , function Object::fooMultipleDispatch , etc.). An ideal design should not require this kind of maintenance work.
Visitor function example I have only a few Object :: foo dispatchers, it looks like this:
template<> class ObjectFooVisitor<2> : public Visitor { // For Object::foo overrides with two arguments. private: std::tuple<std::array<B*, 2>, std::array<C*, 2>> tupleOfArrays; std::array<int, 2> tupleIndices; // .... }; double Object::fooMultipleDispatch (A* a1, A* a2) { ObjectFooVisitor<2> visitor; a1->accept(visitor); // Stores the dynamic type of a1 a2->accept(visitor); // and a2 into ObjectFooVisitor<2> array data members. return MultipleDispatcher<Object, ObjectFooVisitor<2>, double, 2, 0, index_sequence<0>>(this, visitor).execute(); // 2 because there are two arguments in the Object::foo overloads. }
Thus, the main idea that I am following is to save a tuple of arrays of pointers of dynamic types, and then use this storage to call the appropriate overload. But there must be other designs to improve this work.
If you want to see this, here is my complete solution (compiled by GCC 4.9.2, SFINAE support is required). Feel free to try to refine it to make it more maintainable, but I'm sure a new design is needed.
#include <iostream> #include <array> #include <tuple> #include <type_traits> class Object; class B; class C; class D; class Visitor { public: virtual void visit (B*) = 0; virtual void visit (C*) = 0; virtual void visit (D*) = 0; }; class A { public: virtual void accept (Visitor&) = 0; }; class B : public A { virtual void accept (Visitor&) override; }; class C : public A { virtual void accept (Visitor&) override; }; class D : public A { virtual void accept (Visitor&) override; }; template <int, int> struct ArrayType; // Extra template parameter N needed here to allow std::array of any size. template <int N> struct ArrayType<N,0> { using type = std::array<B*, N>; }; template <int N> struct ArrayType<N,1> { using type = std::array<C*, N>; }; template <int N> struct ArrayType<N,2> { using type = std::array<D*, N>; }; template <int> class ObjectFooVisitor; template<> class ObjectFooVisitor<2> : public Visitor { // For Object::foo overrides with two arguments. private: std::tuple<std::array<B*, 2>, std::array<C*, 2>> tupleOfArrays; std::array<int, 2> tupleIndices; int numAccepted = 0; protected: virtual void visit (B* b) override {std::get<0>(tupleOfArrays)[numAccepted] = b; tupleIndices[numAccepted++] = 0;} virtual void visit (C* c) override {std::get<1>(tupleOfArrays)[numAccepted] = c; tupleIndices[numAccepted++] = 1;} virtual void visit (D*) override {} public: ObjectFooVisitor() { std::get<0>(tupleOfArrays) = {{nullptr, nullptr}}; std::get<1>(tupleOfArrays) = {{nullptr, nullptr}}; } template <int N> const typename ArrayType<2,N>::type& getArray() const {return std::get<N>(tupleOfArrays);} const std::array<int, 2>& getTupleIndices() const {return tupleIndices;} }; template<> class ObjectFooVisitor<3> : public Visitor { // For Object::foo overrides with three arguments. private: std::tuple<std::array<B*, 3>, std::array<C*, 3>, std::array<D*, 3>> tupleOfArrays; std::array<int, 3> tupleIndices; int numAccepted = 0; protected: virtual void visit (B* b) override {std::get<0>(tupleOfArrays)[numAccepted] = b; tupleIndices[numAccepted++] = 0;} virtual void visit (C* c) override {std::get<1>(tupleOfArrays)[numAccepted] = c; tupleIndices[numAccepted++] = 1;} virtual void visit (D* d) override {std::get<2>(tupleOfArrays)[numAccepted] = d; tupleIndices[numAccepted++] = 2;} public: ObjectFooVisitor() { std::get<0>(tupleOfArrays) = {{nullptr, nullptr, nullptr}}; std::get<1>(tupleOfArrays) = {{nullptr, nullptr, nullptr}}; std::get<2>(tupleOfArrays) = {{nullptr, nullptr, nullptr}}; } template <int N> const typename ArrayType<3,N>::type& getArray() const {return std::get<N>(tupleOfArrays);} const std::array<int, 3>& getTupleIndices() const {return tupleIndices;} }; class ObjectBarVisitor : public Visitor { private: std::tuple<std::array<B*, 3>, std::array<C*, 3>, std::array<D*, 3>> tupleOfArrays; std::array<int, 3> tupleIndices; int numAccepted = 0; protected: virtual void visit (B* b) override {std::get<0>(tupleOfArrays)[numAccepted] = b; tupleIndices[numAccepted++] = 0;} virtual void visit (C* c) override {std::get<1>(tupleOfArrays)[numAccepted] = c; tupleIndices[numAccepted++] = 1;} virtual void visit (D* d) override {std::get<2>(tupleOfArrays)[numAccepted] = d; tupleIndices[numAccepted++] = 2;} public: ObjectBarVisitor() { std::get<0>(tupleOfArrays) = {{nullptr, nullptr, nullptr}}; std::get<1>(tupleOfArrays) = {{nullptr, nullptr, nullptr}}; std::get<2>(tupleOfArrays) = {{nullptr, nullptr, nullptr}}; } template <int N> const typename ArrayType<3,N>::type& getArray() const {return std::get<N>(tupleOfArrays);} const std::array<int, 3>& getTupleIndices() const {return tupleIndices;} }; class ThingBazVisitor : public Visitor { private: std::tuple<std::array<B*, 3>, std::array<C*, 3>, std::array<D*, 3>> tupleOfArrays; std::array<int, 3> tupleIndices; int numAccepted = 0; protected: virtual void visit (B* b) override {std::get<0>(tupleOfArrays)[numAccepted] = b; tupleIndices[numAccepted++] = 0;} virtual void visit (C* c) override {std::get<1>(tupleOfArrays)[numAccepted] = c; tupleIndices[numAccepted++] = 1;} virtual void visit (D* d) override {std::get<2>(tupleOfArrays)[numAccepted] = d; tupleIndices[numAccepted++] = 2;} public: ThingBazVisitor() { std::get<0>(tupleOfArrays) = {{nullptr, nullptr, nullptr}}; std::get<1>(tupleOfArrays) = {{nullptr, nullptr, nullptr}}; std::get<2>(tupleOfArrays) = {{nullptr, nullptr, nullptr}}; } template <int N> const typename ArrayType<3,N>::type& getArray() const {return std::get<N>(tupleOfArrays);} const std::array<int, 3>& getTupleIndices() const {return tupleIndices;} }; void B::accept (Visitor& visitor) {visitor.visit(this);} void C::accept (Visitor& visitor) {visitor.visit(this);} void D::accept (Visitor& visitor) {visitor.visit(this);} class Object { public: virtual double foo (A*, A*) { std::cout << "Object::foo A,A\n"; return 3.14; } virtual double foo (B*, B*) { std::cout << "Object::foo B,B\n"; return 3.14; } virtual double foo (B*, C*) { std::cout << "Object::foo B,C\n"; return 3.14; } virtual double foo (C*, B*) { std::cout << "Object::foo C,B\n"; return 3.14; } virtual double foo (C*, C*) { std::cout << "Object::foo C,C\n"; return 3.14; } virtual char foo (A*, A*, A*) const { std::cout << "Object::foo A,A,A\n"; return '&'; } // This is needed for the foo overload to be multiple dispatched, even if it is never used, otherwise the other possible foo overloads with three arguments will have no place to go to. virtual char foo (C*, B*, D*) const { std::cout << "Object::foo C,B,D\n"; return '!'; } // Overload of foo with three arguments. Furthermore, the function itself is const and returns char instead of double. Simply define char fooMultipleDispatch (A*, A*, A*), ObjectFooVisitor<3> (the old ObjectFooVisitor now renamed to ObjectFooVisitor<2>) and overload multipleDispatch with char multipleDispatch (const ObjectFooVisitor<3>& visitor, const Z1<Is...>&, const Z2<Js...>&). virtual void bar (A*, A*, A*) const { std::cout << "Object::bar A,A,A\n"; } virtual void bar (B*, B*, B*) const { std::cout << "Object::bar B,B,B\n"; } virtual void bar (B*, C*, B*) const { std::cout << "Object::bar B,C,B\n"; } virtual void bar (B*, C*, C*) const { std::cout << "Object::bar B,C,C\n"; } virtual void bar (B*, C*, D*) const { std::cout << "Object::bar B,C,D\n"; } virtual void bar (C*, B*, D*) const { std::cout << "Object::bar C,B,D\n"; } virtual void bar (C*, C*, C*) const { std::cout << "Object::bar C,C,C\n"; } virtual void bar (D*, B*, C*) const { std::cout << "Object::bar D,B,C\n"; } double fooMultipleDispatch (A*, A*); char fooMultipleDispatch (A*, A*, A*); void barMultipleDispatch (A*, A*, A*); template <template <int...> class Z1, template <int...> class Z2, int... Is, int... Js> double multipleDispatch (const ObjectFooVisitor<2>& visitor, const Z1<Is...>&, const Z2<Js...>&) {return foo (visitor.getArray<Is>()[Js]...);} template <template <int...> class Z1, template <int...> class Z2, int... Is, int... Js> char multipleDispatch (const ObjectFooVisitor<3>& visitor, const Z1<Is...>&, const Z2<Js...>&) const {return foo (visitor.getArray<Is>()[Js]...);} template <template <int...> class Z1, template <int...> class Z2, int... Is, int... Js> void multipleDispatch (const ObjectBarVisitor& visitor, const Z1<Is...>&, const Z2<Js...>&) {bar (visitor.getArray<Is>()[Js]...);} }; class Thing { public: virtual int baz (A*, A*, A*) { std::cout << "Thing::baz A,A,A\n"; return 5; } virtual int baz (B*, B*, B*) { std::cout << "Thing::baz B,B,B\n"; return 5; } virtual int baz (B*, C*, B*) { std::cout << "Thing::baz B,C,B\n"; return 5; } virtual int baz (B*, C*, C*) { std::cout << "Thing::baz B,C,C\n"; return 5; } virtual int baz (B*, C*, D*) { std::cout << "Thing::baz B,C,D\n"; return 5; } virtual int baz (C*, B*, D*) { std::cout << "Thing::baz C,B,D\n"; return 5; } virtual int baz (C*, C*, C*) { std::cout << "Thing::baz C,C,C\n"; return 5; } virtual int baz (D*, B*, C*) { std::cout << "Thing::baz D,B,C\n"; return 5; } int bazMultipleDispatch (A*, A*, A*); template <template <int...> class Z1, template <int...> class Z2, int... Is, int... Js> int multipleDispatch (const ThingBazVisitor& visitor, const Z1<Is...>&, const Z2<Js...>&) {return baz (visitor.getArray<Is>()[Js]...);} // Since Thing only has baz interested in multiple dispatching, it does not need its own MultipleDispatch inner class like Object does (but if other Thing methods want multiple dispatching, then as in the Object::multipleDispatch overloads). }; template <typename, typename, typename, int, int, typename, typename = void, int = 0> struct MultipleDispatcher; template <typename T, typename V, typename R, int Num, int N, template <int...> class Z, int... Is, int I> struct MultipleDispatcher<T, V, R, Num, N, Z<I, Is...>, typename std::enable_if<N != Num-1>::type> : MultipleDispatcher<T, V, R, Num, N, Z<I+1, Is...>, typename std::enable_if<N != Num-1>::type> { T* t; const V& visitor; MultipleDispatcher (T* o, const V& v) : MultipleDispatcher<T, V, R, Num, N, Z<I+1, Is...>, typename std::enable_if<N != Num-1>::type>(o,v), t(o), visitor(v) {} R execute(); }; template <typename T, typename V, typename R, int Num, int Last, template <int...> class Z, int... Is, int I> struct MultipleDispatcher<T, V, R, Num, Last, Z<I, Is...>, typename std::enable_if<Last == Num-1>::type> : MultipleDispatcher<T, V, R, Num, Last, Z<I+1, Is...>, typename std::enable_if<Last == Num-1>::type> { T* t; const V& visitor; MultipleDispatcher (T* o, const V& v) : MultipleDispatcher<T, V, R, Num, Last, Z<I+1, Is...>, typename std::enable_if<Last == Num-1>::type>(o,v), t(o), visitor(v) {} R execute(); }; template <typename T, typename V, typename R, int Num, int N, template <int...> class Z, int... Is> struct MultipleDispatcher<T, V, R, Num, N, Z<Num, Is...>, typename std::enable_if<N != Num-1>::type> { T* t; const V& visitor; MultipleDispatcher (T* o, const V& v) : t(o), visitor(v) {} R execute() {return R();} // End of recursion }; template <typename T, typename V, typename R, int Num, int Last, template <int...> class Z, int... Is> struct MultipleDispatcher<T, V, R, Num, Last, Z<Num, Is...>, typename std::enable_if<Last == Num-1>::type> { // This unique specialization is needed to avoid compiling ambiguity. T* t; const V& visitor; MultipleDispatcher (T* o, const V& v) : t(o), visitor(v) {} R execute() {return R();} // End of recursion }; template <typename T, typename V, typename R, int Num, int N, template <int...> class Z, int... Is, int I> R MultipleDispatcher<T, V, R, Num, N, Z<I, Is...>, typename std::enable_if<N != Num-1>::type>::execute() { if (I == visitor.getTupleIndices()[N]) return MultipleDispatcher<T, V, R, Num, N+1, Z<0, I, Is...>, void>(t, visitor).execute(); // Do we need to specify the std::enable_if part here? Apparently not. We will allow N+1 to be anything, and there is apparently no ambiguity. else return MultipleDispatcher<T, V, R, Num, N, Z<I+1, Is...>, typename std::enable_if<N != Num-1>::type>::execute(); } template <int...> struct index_sequence {}; template <int N, int... Is> struct make_index_sequence_helper : make_index_sequence_helper<N-1, N-1, Is...> {}; template <int... Is> struct make_index_sequence_helper<0, Is...> { using type = index_sequence<Is...>; }; template <int N> using make_index_sequence = typename make_index_sequence_helper<N>::type; template <typename, typename> struct ReverseHelper; template <template <int...> class Z, typename Pack> struct ReverseHelper<Z<>, Pack> { using type = Pack; }; template <template <int...> class Z, int First, int... Rest, int... Is> struct ReverseHelper<Z<First, Rest...>, Z<Is...>> : ReverseHelper<Z<Rest...>, Z<First, Is...>> {}; template <typename> struct Reverse; template <template <int...> class Z, int... Is> struct Reverse<Z<Is...>> : ReverseHelper<Z<Is...>, Z<>> {}; template <typename T, typename V, typename R, int Num, int Last, template <int...> class Z, int... Is, int I> R MultipleDispatcher<T, V, R, Num, Last, Z<I, Is...>, typename std::enable_if<Last == Num-1>::type>::execute() { if (I == visitor.getTupleIndices()[Last]) return t->template multipleDispatch (visitor, typename Reverse<Z<I, Is...>>::type{}, make_index_sequence<Num>{}); // This compiles on GCC 4.9.2 but not on GCC 4.8.1. Template disambiguator needed. else return MultipleDispatcher<T, V, R, Num, Last, Z<I+1, Is...>, typename std::enable_if<Last == Num-1>::type>::execute(); } double Object::fooMultipleDispatch (A* a1, A* a2) { ObjectFooVisitor<2> visitor; a1->accept(visitor); // Stores the dynamic type of a1 a2->accept(visitor); // and a2 into ObjectFooVisitor<2> array data members. return MultipleDispatcher<Object, ObjectFooVisitor<2>, double, 2, 0, index_sequence<0>>(this, visitor).execute(); // 2 because there are two arguments in the Object::foo overloads. } char Object::fooMultipleDispatch (A* a1, A* a2, A* a3) { ObjectFooVisitor<3> visitor; a1->accept(visitor); a2->accept(visitor); a3->accept(visitor); return MultipleDispatcher<Object, ObjectFooVisitor<3>, char, 3, 0, index_sequence<0>>(this, visitor).execute(); // 3 because there are three arguments in this particular Object::foo overload. } void Object::barMultipleDispatch (A* a1, A* a2, A* a3) { ObjectBarVisitor visitor; a1->accept(visitor); a2->accept(visitor); a3->accept(visitor); MultipleDispatcher<Object, ObjectBarVisitor, void, 3, 0, index_sequence<0>>(this, visitor).execute(); // 3 because there are two arguments in the Object::foo overloads. } int Thing::bazMultipleDispatch (A* a1, A* a2, A* a3) { ThingBazVisitor visitor; a1->accept(visitor); a2->accept(visitor); a3->accept(visitor); return MultipleDispatcher<Thing, ThingBazVisitor, int, 3, 0, index_sequence<0>>(this, visitor).execute(); // 3 because there are three arguments in the Thing::baz overloads. } // Test int main() { A* a[] = {new B, new C, new D}; Object* object = new Object; double d = object->foo (a[0], a[1]); // Object::foo A,A (no multiple dispatch) d = object->fooMultipleDispatch (a[0], a[1]); // Object::foo B,C std::cout << "d = " << d << std::endl; // 3.12 const char k = object->fooMultipleDispatch (a[1], a[0], a[2]); // Object::foo C,B,D std::cout << "k = " << k << std::endl; // ! object->bar (a[1], a[0], a[2]); // Object::bar A,A,A (no multiple dispatch) object->barMultipleDispatch (a[1], a[0], a[2]); // Object::bar C,B,D Thing* thing = new Thing; int num = thing->baz (a[1], a[0], a[2]); // Thing::baz A,A,A (no multiple dispatch) num = thing->bazMultipleDispatch (a[1], a[0], a[2]); // Thing::baz C,B,D std::cout << "num = " << num << std::endl; // 5 }