Search code examples
c++templatesmetaprogrammingvariadic-templates

Variadic template as first arguments


I want to write a generic template function that accepts and calls a number of different functions and performs additional setup and teardown operations. The functions signatures differ in the first arguments, such as:

void foo(int i, void* self, (*callback)(void*, int i));
void bar(bool s, bool r, void* self, (*callback)(void*, int i));

I also want to pick out the self argument and the callback, but letting the arguments in front of them vary. I understand this must be hard to do with variadric templates due to how the unpacking works; is there any way around it?


Solution

  • Depending on your use-case, you can either accept callback and self as first arguments, before a parameter pack, and then merge those two in proper order:

    #include <tuple>
    #include <functional>
    
    template <typename F, typename... Ts>
    void wrap(F f, void* self, void (*callback)(void*, int), Ts&&... ts)
    {
        // change self and callback
        std::apply(f, std::tuple_cat(std::forward_as_tuple(std::forward<Ts>(ts)...),
                                     std::make_tuple(self, callback)));
    }
    

    DEMO

    Or extract arguments based on their position in the parameter pack:

    #include <tuple>
    #include <functional>
    
    template <typename F, typename... Ts>
    void wrap(F f, Ts&&... ts)
    {
        constexpr int N = sizeof...(Ts);
        auto args = std::tie(ts...);
    
        void*& self = std::get<N-2>(args);
        void (*&callback)(void*, int) = std::get<N-1>(args);
    
        // change self and callback
        std::apply(f, args);
    }
    

    With retaining value categories the above becomes:

    #include <tuple>
    #include <cstddef>
    #include <utility>
    
    template <typename Tuple, std::size_t... Is>
    auto take(Tuple&& t, std::index_sequence<Is...>)
    {
        return std::forward_as_tuple(std::get<Is>(std::forward<Tuple>(t))...);
    }
    
    template <typename F, typename... Ts>
    void wrap(F f, Ts&&... ts)
    {
        constexpr int N = sizeof...(Ts);
        auto args = std::tie(ts...);
    
        void* self = std::get<N-2>(args);
        void (*callback)(void*, int) = std::get<N-1>(args);
    
        auto firsts = take(std::forward_as_tuple(std::forward<Ts>(ts)...),
                           std::make_index_sequence<N-2>{});
    
        // change self and callback    
        std::apply(f, std::tuple_cat(std::move(firsts), std::make_tuple(self, callback)));
    }
    

    DEMO 2