Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
86 views
in Technique[技术] by (71.8m points)

c++ - Multiple dispatch solution with full maintainability

Can someone think of a good way to implement multiple dispatch with something like the 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
";  return 3.14; }
        virtual double foo (B*, B*) { std::cout << "Object::foo B,B
";  return 3.14; }
        virtual double foo (B*, C*) { std::cout << "Object::foo B,C
";  return 3.14; }
        virtual double foo (C*, B*) { std::cout << "Object::foo C,B
";  return 3.14; }
        virtual double foo (C*, C*) { std::cout << "Object::foo C,C
";  return 3.14; }
        virtual char foo (A*, A*, A*) const { std::cout << "Object::foo A,A,A
";  return '&'; }
        virtual char foo (C*, B*, D*) const { std::cout << "Object::foo C,B,D
";  return '!'; }  // Overload of foo with three arguments.
        virtual void bar (A*, A*, A*) const { std::cout << "Object::bar A,A,A
"; }
        virtual void bar (B*, B*, B*) const { std::cout << "Object::bar B,B,B
"; }
        virtual void bar (B*, C*, B*) const { std::cout << "Object::bar B,C,B
"; }
        virtual void bar (B*, C*, C*) const { std::cout << "Object::bar B,C,C
"; }
        virtual void bar (B*, C*, D*) const { std::cout << "Object::bar B,C,D
"; }
        virtual void bar (C*, B*, D*) const { std::cout << "Object::bar C,B,D
"; }
        virtual void bar (C*, C*, C*) const { std::cout << "Object::bar C,C,C
"; }
        virtual void bar (D*, B*, C*) const { std::cout << "Object::bar D,B,C
"; }
        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]...);} 
};

The client code I have working to carry out the multiple dispatch looks like this:

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
}

You can deduce by my design that my solution fails miserably in the maintenance category. Every time a new function with overloads is to be multiple dispatched, a new corresponding Visitor class, etc... (e.g. in this case ObjectFooVisitor, the function Object::fooMultipleDispatch, etc...) needs to be written. The ideal design should not require this type of maintenance work.

An example Visitor function I have just to multiple dispatch Object::foo, 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>'s 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.
}

So the main idea I'm following is to store a tuple of arrays of pointers of the dynamic types and then use this storage to call up the appropriate overload. But there must be other designs to get this working better.

In case you want to see it, here is my full solution (compiles on GCC 4.9.2, SFINAE support needed). Feel free to try to refine it so that it is 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
";  return 3.14; }
        virtual double foo (B*, B*) { std::cout << "Object::foo B,B
";  return 3.14; }
        virtual double foo (B*, C*) { std::cout << "Object::foo B,C
";  return 3.14; }
        virtual double foo (C*, B*) { std::cout << "Object::foo C,B
";  return 3.14

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I would use something like this, after some brushing up:

requisite headers

#include <iostream>
#include <typeinfo>
#include <map>
#include <array>
#include <functional>
#include <stdexcept>
#include <algorithm>

dynamic_call: call any function with downcasted arguments, used by the dispatcher

// base case: no arguments    

template<typename Result>
Result dynamic_call (std::function<Result()> fun)
{
    return fun();
}    
template<typename Result>
Result dynamic_call (Result(*fun)())
{
    return fun();
}

// one or more argument: dynamic_cast the first argument,
// recursively pass down the rest of them

template<typename Result, typename Arg0, typename FunArg0, typename ... Args, typename ... FunArgs>
Result dynamic_call (std::function<Result(FunArg0*, FunArgs*...)> fun, Arg0* arg0, Args*... args)
{
    FunArg0* converted_arg0 = dynamic_cast<FunArg0*>(arg0);
    if (converted_arg0 == nullptr)
        throw std::runtime_error("Argument type error!");
    std::function<Result(FunArgs*...)> helper = [converted_arg0, fun](FunArgs*... fun_args) -> Result
    {
        return fun(converted_arg0, fun_args...);
    };
    return dynamic_call(helper, args...);
}

template<typename Result, typename Arg0, typename FunArg0, typename ... Args, typename ... FunArgs>
Result dynamic_call (Result (*fun)(FunArg0*, FunArgs*...), Arg0* arg0, Args*... args)
{
    std::function<Result(FunArg0*, FunArgs*...)> sfn(fun);
    return dynamic_call(sfn, arg0, args...);
}

dispatcher: store a bunch of functions in a map, find them by actual dynamic types of passed arguments

template <typename Result, typename ... Args>
class Dispatcher
{
  public:

    Result operator() (Args*... args)
    {
        key k{tiholder(typeid(*args))...};
        typename map::iterator it = functions.find(k);
        if (it == functions.end())
            throw std::runtime_error("Function not found!");
        return it->second(args...);
    }

    template <typename ... FunArgs>
    void register_fn(std::function<Result(FunArgs*...)> fun)
    {
        auto lam = [fun](Args*... args) -> Result
        {
            return dynamic_call(fun, args...);
        };
        key k{tiholder(typeid(FunArgs))...};
        functions[k] = lam;
    }

    template <typename ... FunArgs>
    void register_fn(Result(*fun)(FunArgs*...))
    {
        return register_fn(std::function<Result(FunArgs*...)>(fun));
    }

  private:

    struct tiholder
    {
        const std::type_info* ti;
        tiholder(const std::type_info& ti) : ti(&ti) {}
        bool operator< (const tiholder& other) const { return ti->before(*other.ti); }
    };

    static constexpr int PackSize = sizeof ... (Args);
    using key = std::array<tiholder, PackSize>;
    using value = std::function<Result(Args*...)>;
    using map = std::map<key, value>;
    map functions;
};

test case

struct Base { virtual ~Base() {} } ;

struct A : Base {};
struct B : Base {};

void foo1(A*,A*) { std::cout << "foo(A*,A*)
"; }
void foo2(A*,B*) { std::cout << "foo(A*,B*)
"; }
void foo3(B*,A*) { std::cout << "foo(B*,A*)
"; }
void foo4(B*,B*) { std::cout << "foo(B*,B*)
"; }

test driver and user guide

int main ()
{
    Base* x = new A;
    Base* y = new B;

    Dispatcher<void,Base,Base> foo;
    foo.register_fn(foo1);
    foo.register_fn(foo2);
    foo.register_fn(foo3);
    foo.register_fn(foo4);

    foo(x,x);
    foo(x,y);
    foo(y,x);
    foo(y,y);

}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...