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(); // <-- ???
}
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.
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.
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.
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.
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)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With