Placing an arbitrary number of function calls into a function argument list using templates

While I was trying to generate code to bind C++ to a scripting language, I discovered that C++ templates, in their current modern form, are not as scary as I used to think they were. Here are a few useful things that I discovered.

First, in toy form, the problem that I was trying to solve. Suppose I have a free function that I want to bind to a virtual machine.

1
2
3
double convertToDegrees( double rad ) {
    return rad * ( 180.0 / 3.14159265359 );
}

Communication with the virtual machine is carried out with these template functions.

1
2
3
4
5
6
7
// When called, returns the argument at position index within the argument list.
template< typename T>
T GetArgument( int index );

// when called, returns the value to the function in the virtual machine.
template< typename T >
void Return( T val );

I could wrap convertToDegrees in the virtual machine glue code as follows, and give it to the virtual machine’s C API.

1
2
3
4
void wrapper() {
    double res = convertToDegrees( GetArgument<double>( 1 ) );
    Return( res );
}

But writing this for every function will take time. Additionally, if I were to change the function definition, then I would have to alter the wrapper manually. I want to generate these bindings automatically.

Given a free function, I want to generate the appropriate calls to GetArgument and place them in the correct order in the function call. Thus, a way to index and get a function’s argument types is needed.

Function traits

Storing function traits can be done easily.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
template< typename F >
struct function_traits;

template< typename R, typename... Args >
struct function_traits< R( Args... ) > {
    using return_type = R;

    constexpr static const std::size_t arity = sizeof...( Args );

    template< std::size_t N >
    struct argument {
        static_assert( N < arity, "FunctionTraits error: invalid argument count parameter" );
        using type = std::tuple_element_t< N, std::tuple< Args... > >;
    };

    template< std::size_t N >
    using argument_type = typename argument<N>::type;
};

The class can be used like this:

1
2
3
using traits = function_traits< decltype(convertToDegrees) >;
cout << is_same<double, traits::argument_type<0>>::value << endl;
cout << is_same<double, traits::return_type>::value << endl;

You can use the decltype specifier to get the function’s type, or just enter it manually. For convertToDegrees, the type would be double(double).

Unpacking a tuple into a function argument list

Tuples can be unpacked into a function call surprisingly easily. Use std::index_sequence to generate a non-type parameter pack of indices to index the tuple elements at compile time.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
template< typename Function, typename Tuple, std::size_t... index>
decltype( auto ) invokeHelper( Function&& f, Tuple&& tup, std::index_sequence<index...> ) {
    return f( std::get<index>( std::forward<Tuple>(tup) )... );
}

template< typename Function, typename Tuple >
decltype( auto ) invokeWithTuple( Function&& f, Tuple&& tup ) {
    constexpr auto Arity = function_traits< std::remove_reference_t<decltype(f)> >::arity;
    return invoke_helper( 
        std::forward<Function>( f ), 
        std::forward<Tuple>( tup ), 
        std::make_index_sequence<Arity>{} 
    );
}

How exactly does this work? When the ellipsis operator is placed after a pattern containing a variadic parameter, the pattern is instantiated for each instance of the variadic parameter. The instances are separated by commas. In invokeHelper, the (non-type) variadic parameter is index.

Note the C++14-ism in the above code. In C++11, you would write function_traits< typename std::remove_reference<decltype(f)>::type >. C++14 introduced aliasing, which allows this: template<typename T> using remove_reference_t = typename remove_reference<T>::type;. All the type traits in type_traits have aliases of this form in C++14.

Now this should work:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void say( int val, double val2 ) {
    std::cout << "called with " << val << ", " << val2 << std::endl;
}

int main() {
    auto tup = std::make_tuple( 5, 42.0 );
    invokeWithTuple( say, tup );

    return 0;
}

Placing GetArgument<T> into a function argument list

We can place the calls to GetArgument<T> in a very similar way.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
template< typename Function, std::size_t... index>
decltype( auto ) invokeHelper( Function&& f, std::index_sequence<index...> ) {
  using traits = function_traits< std::remove_reference_t<decltype(f)> >;
  return f( GetArgument< typename traits::template argument_type<index> >( index + 1 )... );
}

template< typename Function >
decltype( auto ) invokeWithArguments( Function&& f ) {
  constexpr auto Arity = function_traits< std::remove_reference_t<decltype(f)> >::arity;
  return invoke_helper( 
    std::forward<Function>( f ),
    std::make_index_sequence<Arity>{} 
  );
}

Generate a unique wrapper class for a function

The last piece of my toy puzzle. I call invokeWithArguments from a static method, so that I can give the static method to the virtual machine’s C API.

1
2
3
4
5
6
7
8
9
template< typename Signature, Signature& >
struct ForeignMethodWrapper;

template< typename R, typename... Args, R( &P )( Args... ) >
struct ForeignMethodWrapper< R( Args... ), P > {
    static void call() {
        invokeWithArguments( P );
    }
};

Invoking code conditionally

Actually, I’m not done yet. I still haven’t explained how I call Return<T>( T val ) only when the function’s return value is not void. A specialized struct can be used to store the different invocations.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// to be used with std::is_void
template<bool predicate>
struct InvokeWithoutReturningIf {
  template< typename Function >
  static void invoke( Function&& f ) {
    invokeWithArguments( std::forward< Function >( f ) );
  }
};

template<>
struct InvokeWithoutReturningIf<false> {
  template< typename Function >
  static void invoke( Function&& f ) {
    using R = function_traits< std::remove_reference_t<decltype(f)> >::return_type;
    Return<R>( invokeWithArguments( std::forward<Function>(f) ) );
    }
};

Finally, I modified ForeignMethodWrapper to

1
2
3
4
5
6
template< typename R, typename... Args, R( &P )( Args... ) >
struct ForeignMethodWrapper< R( Args... ), P > {
  static void call() {
    InvokeWithoutReturningIf< std::is_void<R>::value >::invoke( P );
  }
};

which completes the solution for my toy problem.

Contents