By Vermeille on Tuesday 12 March 2013, 17:00 - C++ - Permalink

Introduction

Global View

This article isn't about automata, they just are a pretext for me to introduce the issue solved with this bridge-design

I'm currently studying at the Epita Research and Development Laboratory, working on the Vaucanson project. Because of the genericity we want, we make an overuse of template programming. We have automata templated with contexts, algorithms valids with automaton of precise contexts and... we wanted to put all this people in the same house. To be more precise, we have automata that can be templated with chars, words, automata and regexp, and labeled with weights in R, Z, Zmin, with epsilons, and bools. Label type and automaton type defines the Context of our automatonSome of our algorithms works with char automaton, ignoring the labelling type. So, our automata are:

template < class Context> class automaton { ... };

And our algorithms looks like:

template < class Automaton> Automaton Determinize( const Automaton& aut) { ... }

The issue

We are perfectly compile-time with this design... But we want to be able to use it a runtime : why the hell should the user recompile to use another kind of automata ?

We have:

Automata templated by their context Algorithms templated by automata. Their prototype are totally free

And we want to be able to instantiate automata of certain type depending on runtime execution (first issue), and then, call algorithms on these automata (second issue)

Dealing with dynamic automata

Automaton handling is quite simple: we just have to inherits our templated automata from a mother class, say dynaut , for dynamic automaton, and manipulate it with a pointer.

template < class Contex> class automaton : public dynaut { ... };

We simply upcast our automaton<T> to dynaut automaton, and then downcast it into their static type when needed for the algorithm. That's a great improvement from calling a virtual function every time! So, our dynamic automaton will contain context informations in a member string, and we have to use that to downcast it.

Algorithms

The second part is more complicated. We want our algorithms to be functors, so that they can hold their code and their documentation in a single entity.

template < class Aut> class Determinize { Aut operator ()( const Aut& aut) { ... } std::string Doc() { return "documentation" ; } };

To use these algorithms we need to instantiate:

The template specialization of this code using Algorithm<automaton<context>> somewhere An instance of this specialization

And the most practical datastructure which comes in mind would be a std::map<std::string, ?Algorithm?> to register our algorithms and call them with register[my_dynaut.context()](my_dynaut);

But it's too simple Remind: algorithms can have any prototype. Prepare yourself to the heat of Hell, we starts C++11's black magick

Abracadabra

The first thing to do is to inherit our algorithms from a non-templated base, itself deriving from an empty class which represents our algorithms.

class Algo { virtual std::string Doc() = 0 ; // documentation }; class DeterminizeBase : public Algo { virtual dynaut& operator ()( const dynaut&) = 0 ; static std::string name() { return "determinize" ; } }; template < class Aut> class Determinize : public DeterminizeBase { Aut operator ()( const Aut& aut) { ... } std::string Doc() { return "documentation" ; } dynaut& operator ()( const dynaut& aut) { return operator ()( dynamic_cast <Aut&>(aut)); } static std::string name() { return std::string( "determinize" ) + Aut::name(); } };

The Algo base class will be useful later. The DeterminizeBase allows to fetch the prototype of Determinize (as any other algorithm) without any consideration of the automaton type. See how the actual implementation in Determinize downcasts the dynaut in order to call the real implementation, using overload.

Our register can now be a std::map<std::string, std::unique_ptr<Algo>> and we need to wrap it in a class to handle function calls and registration.

Above, we see that we included methods name() which provides us unique identifiers for those methods, for indexing them in the register

class AlgoReg { private : std::map<std::string, std::unique_ptr<Algo>> reg_; public : template < class Algorithm> void Register() { reg_[Algorithm::name()] = std::make_unique<Algorithm>(); } ??? Call(???) { ??? } };

The Register() method is straightforward. We can use it like that:

AlgosReg reg; //you can turn it to a singleton reg.Register<Determinize<automaton<context_lal_char_b>>>();

The Call() is really much more complicated. Focus on prototype. First, we want to be able to tell which algorithm we want:

template < class Algorithm> ??? Call(???) { ??? }

We don't know how much parameters takes the algorithm, nor their type:

template < class Algorithm, class ... Args> ??? Call(Args&&... args) { ??? } //The move semantics is not mandatory

And it should return the value returned by the algorithm itself:

template < class Algorithm, class ... Args> auto Call(Args&&... args) -> decltype (std::declval<Algorithm>()(args...)) { ??? }

This deserves a deeper explanation. decltype is a C++11 keyword which evaluates to the type of the expression in argument ; decltype (3) a; is equivalent to int a; . std::declval is made for use in decltype expressions, and returns an "instance" of its template parameter. We then call operator() with provided args, and all this expression evaluates to the return type of our algorithm.

Dive into the implementation. We have to build the string corresponding to the right instanciation we have to call. In order to do that, we have to iterate through arguments, and concatenate string-context of our dynamics automata. This concept is shown here:

class VarStringBuilder { // if there is no argument, return the empty string static inline std::string Build() { return "" ; } // else, if the first argument is a dynaut, extract its context, then // recurse on the next argument template < class T, class ... Ts> static inline std::enable_if<std::is_same<T, dynaut>::value, std::string>::type Build( const T& dynaut, Ts... args) { return dynaut.ctx + VarStringBuilder::Build(args...); } template < class T, class ... Ts> static inline std::enable_if<!std::is_same<T, dynaut>::value, std::string>::type Build( const T& dynaut, Ts... args) { return VarStringBuilder::Build(args...); } };

And we're almost done! The final code of our Call() method is:

template < class Algorithm, class ... Args> auto Call(Args&&... args) -> decltype (std::declval<Algorithm>()(args...)) { auto fun = reg_[Algorithm::name() + VarStringBuilder::Build(args...)] return (*dynamic_pointer_cast<Algorithm>(fun))(args...); }

And we enabled compile-time time checking, on a function with a complete variadic signature. Use it:

AlgosReg reg; // Really, make it as a singleton reg.Register<Determinize<automaton<lal_char_b>>(); //register ... automaton<lal_char_b> static_aut; dynaut& da = static_aut; reg.Call<DeterminizeBase>(da);

Conclusion