Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic dispatching of template functions?

Is it possible to decide in run-time which template function to call? Something like:

template<int I>
struct A {
    static void foo() {/*...*/}
};

void bar(int i) {
    A<i>::f();   // <-- ???
}
like image 375
Predrag Avatar asked Aug 17 '11 07:08

Predrag


People also ask

What is dynamic dispatch in JavaScript?

Dynamic dispatch is a defining feature of object-oriented programming. So what is so special about it? Static dispatch (or early binding) happens when I know at compile time which function body will be executed when I call a method.

What is dynamic dispatch in OOP?

In computer science, dynamic dispatch is the process of selecting which implementation of a polymorphic operation ( method or function) to call at run time. It is commonly employed in, and considered a prime characteristic of, object-oriented programming (OOP) languages and systems.

What is the difference between dynamic dispatch and late binding?

While dynamic dispatch does not imply late binding, late binding does imply dynamic dispatch, since the implementation of a late-bound operation is not known until run time. The choice of which version of a method to call may be based either on a single object, or on a combination of objects.

What is the difference between dynamic dispatch and open recursion?

But dynamic dispatch allows much more. In object-oriented languages, a subclass can change the behaviour of a base class by overriding some methods. The ability of a base class to call a method that is resolved through dynamic dispatch to a subclass method is called open recursion.


1 Answers

A typical 'trick' to bridge compile time and runtime when dealing with templates is visiting a variant type. That's what the Generic Image Library (available as Boost.GIL or standalone) does for instance. It typically takes the form of:

typedef boost::variant<T, U, V> variant_type;
variant_type variant = /* type is picked at runtime */
boost::apply_visitor(visitor(), variant);

where visitor is a polymorphic functor that simply forwards to the template:

struct visitor: boost::static_visitor<> {
    template<typename T>
    void
    operator()(T const& t) const
    { foo(t); } // the real work is in template<typename T> void foo(T const&);
};

This has the nice design that the list of types that the template will/can be instantiated with (here, the variant_type type synonym) is not coupled to the rest of the code. Metafunctions like boost::make_variant_over also allows computations over the list of types to use.

Since this technique is not available to non-type parameters, you need to 'unroll' the visitation by hand, which unfortunately means the code is not as readable/maintainable.

void
bar(int i) {
    switch(i) {
        case 0: A<0>::f(); break;
        case 1: A<1>::f(); break;
        case 2: A<2>::f(); break;

        default:
            // handle
    }
}

The usual way to deal with the repetition in the above switch is to (ab)use the preprocessor. An (untested) example using Boost.Preprocessor:

#ifndef LIMIT
 #define LIMIT 20 // 'reasonable' default if nothing is supplied at build time
#endif
#define PASTE(rep, n, _) case n: A< n >::f(); break;

void
bar(int i) {
    switch(i) {
        BOOST_PP_REPEAT(LIMIT, PASTE, _)

        default:
            // handle
    }
}

#undef PASTE
#undef LIMIT

Better find good, self-documenting names for LIMIT (wouldn't hurt for PASTE either), and limit the above code-generation to just one site.


Building from David's solution and your comments:

template<int... Indices>
struct indices {
    typedef indices<Indices..., sizeof...(Indices)> next;
};

template<int N>
struct build_indices {
    typedef typename build_indices<N - 1>::type::next type;
};

template<>
struct build_indices<0> {
    typedef indices<> type;
};

template<int... Indices>
void
bar(int i, indices<Indices...>)
{
    static void (*lookup[])() = { &A<Indices>::f... };
    lookup[i]();
}

then to call bar: bar(i, typename build_indices<N>::type()) where N would be your constant-time constant, sizeof...(something). You can add a layer to hide the 'ugliness' of that call:

template<int N>
void
bar(int i)
{ bar(i, typename build_indices<N>::type()); }

which is called as bar<N>(i).

like image 191
Luc Danton Avatar answered Oct 05 '22 12:10

Luc Danton