Why C++: Template metaprogramming (part 2)

(This is a direct continuation of part1)

Implementation

Given the structures defined in the previous section, we can now write a templated function that will be the basis for the compiler to generate a wrapper for each function we'd like to expose to the script engine. The signature of this function needs to be exactly the same as the one of MyFunction declared in the previous post.

Inside it, we need to perform the following essential steps:

  • Transform all the parameters we receive from the script engine from const data_type* to the types we need to call the script function
  • Call the script function with these transformed values
  • Receive the return value of this call and transform it to result_type

Let's start off by defining a function template for converting a single parameter from the data_type array to a given type R. It can look like this:

// I is the index into params
template<typename R, unsigned int I>  
struct extract_ {  
    inline static R extract(ScriptEngine*&&, const data_type*&& params) noexcept {
        return static_cast<R>(forward<const data_type*>(params)[I]);
    }
};

Most likely, you will want to specialize this template for various R in case the conversion takes more than just a simple static_cast.

With this building block in place, we can move on to implementing the centerpiece: a templated function performing the call to the script function. Since the call will involve passing an arbitrary number of arguments of arbitrary types, we will be employing a variadic template. Variadic templates are one of the major new features of modern C++ you have to know! ☕

template<unsigned int I, unsigned int F>  
struct dispatch_ {  
    template<typename R, typename... Args>
    inline static R dispatch(ScriptEngine*&& se, const data_type*&& params, Args&&... args) noexcept {
        constexpr ScriptFunction const& F_ = functions[F];
        return dispatch_<I - 1, F>::template dispatch<R>(forward<ScriptEngine*>(se),
                                                         forward<const data_type*>(params),
                                                         extract_<typename CharType<F_.func.types[I - 1]>::type, I>::extract(forward<ScriptEngine*>(se),
                                                                                                                             forward<const data_type*>(params)),
                                                         forward<Args>(args)...);
    }
};

template<unsigned int F>  
struct dispatch_<0, F> {  
    template<typename R, typename... Args>
    inline static R dispatch(ScriptEngine*&&, const data_type*&&, Args&&... args) noexcept {
        constexpr ScriptFunction const& F_ = functions[F];
        return reinterpret_cast<R(*)(...)>(F_.func.addr)(forward<Args>(args)...);
    }
};

The dispatch_ struct will be instantiated with I, which is the index of the next argument we will extract, and F which is the index into the constant expression functions array we declared earlier indicating which script function we will be calling in the end. The dispatch function contained within makes use of R which is the return type of the script function and Args, which is the parameter pack we are going to build by processing each argument recursively.

Initially, Args will be empty: while I is greater than zero, we will extract a single argument using extract, decrease I by one and instantiate dispatch_ again until we have extracted all arguments and I reaches zero. A specialization for dispatch_ exists for this particular case, effectively terminating the recursion; a pattern which is commonly used when programming with templates. In it, the actual call to the script function is performed and the return value of it given back to the caller.

Note that the arguments are extracted in reverse order: we start using the last ("rightmost") one and stop with the first ("leftmost"). The script function is then, however, called with the arguments in the expected order.

CharType is a template which is essentially TypeChar with its mapping reversed: it is being used to map the argument type (which we encoded as a character) back into an actual type which will be forwarded to extract.

(side note: one major improvement I'd like to see in this implementation is replacing the ellipsis in the reinterpret_cast with a proper type. I believe it should be possible to instantiate this function type using the information we encoded in the ScriptFunction structure, but I haven't attempted it yet)

Now we can wrap it up and specify a function having the MyFunction signature which I mentioned initially: quite simple now!

template<unsigned int F>  
static result_type wrapper(ScriptEngine* se, const data_type* params) noexcept {  
    auto result = dispatch_<functions[F].func.numargs, F>::template dispatch<typename CharType<functions[F].func.ret>::type>(forward<ScriptEngine*>(se), forward<const data_type*>(params));
    return static_cast<result_type>(result);
}

You may want to implement a template similar to extract which is performing a proper conversion from the type of result to result_type if necessary.

A gotcha, then: this will fail to compile if the script function's return type is void. You can specialize this template with the help of enable_if / SFINAE to work around this.

Conclusion

At the end of the previous part of this blog post, I mentioned we'd end up only having to add a single line per script function to the functions array and recompile to be done. We didn't achieve this yet! For automatically instantiating all the wrappers, we will let the compiler create another array containing them.

typedef result_type (*function_type)(ScriptEngine*, const data_type*);  
constexpr auto functions_num = sizeof(functions) / sizeof(functions[0]);  
using functions_seq = make_index_sequence<functions_num>;

struct ScriptWrapper  
{
    const char* name;
    const function_type func;

    constexpr ScriptWrapper(const char* name, function_type func) : name(name), func(func) {}
};

template<size_t... Is>  
static const ScriptWrapper* wrappers(index_sequence<Is...>) {  
    static constexpr ScriptWrapper wrappers_[] {
        {functions[Is].name, wrapper<Is>}...
    };

    constexpr auto wrapped_num = sizeof(wrappers_) / sizeof(wrappers_[0]);
    static_assert(functions_num == wrapped_num, "Not all functions wrapped");

    return wrappers_;
}

We are using integer_sequence (C++14) to generate a series of indices based on the number of entries in functions, turning the sequence into a parameter pack Is and expanding it into a static array containing the wrapped functions. To retrieve this array at runtime (to register the contained wrapped functions with the script engine), you'd call wrappers(functions_seq()). It should be possible to implement this array without using a function returning it too if necessary.

This is it! A full working implementation of everything described in this and the previous blog post can be found here. It should compile fine using any C++14 compiler (I tried Clang 3.9). If you can suggest any improvements or have questions/comments, feel free to let me know on the GitHub repository or contact me directly. Thanks for reading!