#include <type_traits>
#include <utility>
#include <typeinfo>
#include <cassert>
#include <stdexcept>

template<std::size_t index, class...Ts> struct get_variadic_by_index;
template<std::size_t index, class First, class...Rests> struct get_variadic_by_index<index,First,Rests...> : public get_variadic_by_index<index-1,Rests...> {};
template<class First, class...Rests> struct get_variadic_by_index<0,First,Rests...> {typedef First type;};


template<class func_type, class LBase, class RBase, class LTypes, class RTypes> class double_dispatch;
template<class func_type, class LBase, class RBase, class...Ls, class...Rs> 
class double_dispatch<func_type, LBase, RBase, std::tuple<Ls...>,std::tuple<Rs...>>
{
    typedef decltype(std::declval<func_type>()(
        std::declval<typename get_variadic_by_index<0,Ls...>::type*>(),
        std::declval<typename get_variadic_by_index<0,Rs...>::type*>()
        )) return_type;

    template<int LI, int RI>
    return_type call(LBase* l, RBase* r)
    {
        return func(
            static_cast<typename get_variadic_by_index<LI,Ls...>::type*>(l),
            static_cast<typename get_variadic_by_index<RI,Rs...>::type*>(r)
            );
    }
    
    template<int LI>
    return_type iter_right_types(LBase*, RBase*, const std::type_info&, std::integral_constant<int,sizeof...(Rs)>)
    {assert("typeid(r) is not in tuple list"); throw std::bad_cast();}

    template<int LI, int RI>
    return_type iter_right_types(LBase* l, RBase* r, const std::type_info& righttype, std::integral_constant<int,RI>)
    {
        if (righttype==typeid(typename get_variadic_by_index<RI,Rs...>::type))
            return call<LI,RI>(l, r);
        else
            return iter_right_types<LI>(l, r, righttype, std::integral_constant<int,RI+1>());
    }
    
    return_type iter_left_types(LBase*, RBase*, const std::type_info&, std::integral_constant<int,sizeof...(Ls)>)
    {assert("typeid(l) is not in tuple list"); throw std::bad_cast();}

    template<int LI>
    return_type iter_left_types(LBase* l, RBase* r, const std::type_info& lefttype, std::integral_constant<int,LI>)
    {
        if (lefttype==typeid(typename get_variadic_by_index<LI,Ls...>::type))
            return iter_right_types<LI>(l, r, typeid(*r), std::integral_constant<int,0>());
        else
            return iter_left_types(l, r, lefttype, std::integral_constant<int,LI+1>());
    }
    func_type func;
public:
    explicit double_dispatch(func_type func) : func(func) {}

    return_type operator()(LBase* l, RBase* r) 
    {return iter_left_types(l, r, typeid(*l), std::integral_constant<int,0>());}
    
    return_type dispatch(LBase* l, RBase* r) 
    {return iter_left_types(l, r, typeid(*l), std::integral_constant<int,0>());}
};
#define DOUBLE_DISPATCHER(FUNC) \
struct {\
    template<class L,class R> \
    auto operator()(L&& l, R&& r) \
    ->decltype(FUNC(std::forward<L>(l),std::forward<R>(r))) \
    {return FUNC(std::forward<L>(l),std::forward<R>(r));} \
}



#include <iostream>
struct Loud {
    Loud() {std::cerr << "LOUD: default ctor\n";}
    Loud(const Loud&) {std::cerr << "LOUD: copy ctor\n";}
    Loud(Loud&&) {std::cerr << "LOUD: move ctor\n";}
    ~Loud() {std::cerr << "LOUD: dtor\n";}
    Loud& operator=(const Loud&) {std::cerr << "LOUD: copy assign\n"; return *this;}
    Loud& operator=(Loud&&) {std::cerr << "LOUD: move assign\n"; return *this;}
};
struct Letter {
    virtual ~Letter() {}
};
struct A : public Letter {};
struct B : public Letter {};
struct C : public Letter {};
struct Number {
    virtual ~Number() {}
};
struct One : public Number {};
struct Two : public Number {};
struct Three : public Number {};

Loud print(const A*,const One*)     {std::cerr << "A1\n"; return {};}
Loud print(const A*,const Two*)     {std::cerr << "A2\n"; return {};}
Loud print(const A*,const Three*)   {std::cerr << "A3\n"; return {};}
Loud print(const B*,const One*)     {std::cerr << "B1\n"; return {};}
Loud print(const B*,const Two*)     {std::cerr << "B2\n"; return {};}
Loud print(const B*,const Three*)   {std::cerr << "B3\n"; return {};}
Loud print(const C*,const One*)     {std::cerr << "C1\n"; return {};}
Loud print(const C*,const Two*)     {std::cerr << "C2\n"; return {};}
Loud print(const C*,const Three*)   {std::cerr << "C3\n"; return {};}
DOUBLE_DISPATCHER(print) printdispatch;
double_dispatch<decltype(printdispatch), const Letter, const Number, 
    std::tuple<const A,const B,const C>,
    std::tuple<const One,const Two,const Three>> 
    d_dispatcher(printdispatch);
    
int main() 
{
    const Letter& a = A{};
    const Letter& b = B{};
    const Letter& c = C{};
    const Number& on = One{};
    const Number& tw = Two{};
    const Number& th = Three{};

    d_dispatcher(&a,&on);
    d_dispatcher(&a,&tw);
    d_dispatcher(&a,&th);
    d_dispatcher(&b,&on);
    d_dispatcher(&b,&tw);
    d_dispatcher(&b,&th);
    d_dispatcher(&c,&on);
    d_dispatcher(&c,&tw);
    d_dispatcher(&c,&th);

    return 0;
}