Reizo
Reizo

Reputation: 1437

Allow function of variadic argument count as template function argument

In C++ we can pretty easily define template function arguments as seen in this question. However, this only allows functions with a defined parameter list and return value.

We can make those types template parameters like this:

template<typename R, typename P, R(*Func)(P)>
R proxy_func(P arg)
{
    // do some stuff...
    R&& res{ Func(std::forward(arg)) };
    // do some more stuff...
    return res;
}

However, here we are still limited to a fixed number of parameters.

An attempt like the following won't compile (at least on MSVC 19.24.28315), because "template parameter 'Func' cannot be used because it follows a template parameter pack and cannot be deduced from the function parameters of 'proxy_func'".

template<typename R, typename... P, R(*Func)(P...)>
R proxy_func(P... args)
{
    // do some stuff...
    R&& res{ Func(std::forward(args)...) };
    // do some more stuff...
    return res;
}

So what are the options for allowing an arbitrary function as template argument and - just as importantly - allow its parameters to be used as the parameters for, say, the function template it's used in (like in proxy_func above)? Ideally, I would be able to call proxy_func with just any function as (ideally single) template argument Func and be able to apply its (proxy_func's) arguments to Func.


Edit: I am particularly interested in having a function-instance as template argument (e. g. to allow template instantiations of proxy_func to apply compile-time optimisations based on individual Funcs).

Upvotes: 0

Views: 286

Answers (2)

RedFog
RedFog

Reputation: 1015

thanks to C++17, auto can be used as a placeholder of a non-type template parameter, and then we can pass the function pointer without type declarations:

template<auto func, typename... Args>
auto proxy_func(Args&&... args){
    // do something...
    auto result = func(std::forward<Args>(args)...);
    // do something...
    return result;
}

and it's extremely exhausting before C++17. if you really want, you have to use a wrapper:

int foo(){ return 0; };

template<typename Ret, typename... Args>
struct func_wrapper{
    template<Ret(*func)(Args...)>
    struct helper{
        typedef Ret return_type;
        typedef std::tuple<Args...> parameter_types;
        static constexpr Ret (*value)(Args...) = func;
    };
};
template<typename Ret, typename... Args>
auto helper(Ret(*)(Args...))->func_wrapper<Ret, Args...>;

template<typename Wrapper, typename... Args>
auto proxy_func(Args&&... args)
->decltype(Wrapper::value(std::forward<Args>(args)...)){
    // do something...
    auto result = Wrapper::value(std::forward<Args>(args)...);
    // do something...
    return result;
}

int main(){
    typedef decltype(helper(&foo)) wrapper_klass;
    typedef wrapper_klass::helper<&foo> wrapper_type;
    proxy_func<wrapper_type>();
}

and then, if you want to decide the Args by func, you have to use some tricky way to unpack the arguments pack:

template<auto>
struct proxy_func_klass;
template<typename Ret, typename... Args, Ret(*func)(Args...)>
struct proxy_func_klass<func>{
    static Ret call(Args... args){
        return func(args...);
    }
};

and the C++11 version:

int foo(){ return 0; };

template<typename Ret, typename... Args>
struct func_wrapper{
    template<Ret(*func)(Args...)>
    struct helper{
        typedef Ret return_type;
        typedef std::tuple<Args...> parameter_types;
        static constexpr Ret (*value)(Args...) = func;
    };
};
template<typename Ret, typename... Args>
auto helper(Ret(*)(Args...))->func_wrapper<Ret, Args...>;

template<typename, typename, typename>
struct helper_klass;
template<typename Self, typename Ret, typename... Args>
struct helper_klass<Self, Ret, std::tuple<Args...>>{
    static Ret call(Args... args){
        return Self::value(args...);
    }
};
template<typename Wrapper>
struct proxy_func_klass : public helper_klass<proxy_func_klass<Wrapper>, typename Wrapper::return_type, typename Wrapper::parameter_types>{
    static constexpr auto value = Wrapper::value;
};


int main(){
    typedef decltype(helper(&foo)) wrapper_klass;
    typedef wrapper_klass::helper<&foo> wrapper_type;
    proxy_func_klass<wrapper_type>::call();
}

Upvotes: 1

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 122133

In some sense, the Q&A you link is the complicated way. The simple way is:

template <typename F>
void foo(F f) {
    f();    // call the function
}

In your case the return value can be deduced and the argument types can be deduced from the parameters to foo. Note that they need not necessarily match exactly the argument types of f as long as f can be called with them:

template <typename F,typename ... Args>
auto foo( F f, Args ... args) {
    return f(args...);    // call the function
}

For the sake of brevity I ommitted forwarding and storing the result of the call in a temporary, but the answer still applies.

In case you want to have the function itself, not its type as template parameter (sorry I missed that part first) this still works:

template <auto f,typename ... Args>
auto foo(Args ... args) {
    return f(args...);    // call the function
}

However, this won't work in case f is a lambda. See here for details: https://en.cppreference.com/w/cpp/language/template_parameters#Non-type_template_parameter

Upvotes: 4

Related Questions