0

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).

Reizo
  • 1,374
  • 12
  • 17
  • Compiles fine [here](https://godbolt.org/z/4Yx94j) – Aykhan Hagverdili Oct 07 '20 at 12:59
  • I think the latter part is already handled by compiler-specific function inline optimization. As always, benchmark and read the assembly. – user202729 Oct 07 '20 at 13:42
  • @user202729 yes I would not disagree at all. However, optimization was only an exemplary reason. I was looking for a compile-time solution, regardless of the practical benefits. – Reizo Oct 07 '20 at 14:04

2 Answers2

4

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

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • That's pretty much what I want, thanks! Now - it almost feels too much to ask for - is there maybe even a chance to omit the `Args` *template parameters* of `foo` and somehow specify the parameter pack `args` based on the template function `f`? – Reizo Oct 07 '20 at 14:02
  • Note that the last piece of code only works with function pointer and only work with C++ 17 (https://en.cppreference.com/w/cpp/language/template_parameters#Non-type_template_parameter) (this may change by C++ 20) – user202729 Oct 07 '20 at 14:09
  • @user202729 oh right, I assumed lambdas would be fine, but they are not, will add a note – 463035818_is_not_an_ai Oct 07 '20 at 14:18
  • @idclev463035818, also, sadly this doesn't work with overloading. – Elliott Oct 07 '20 at 14:20
  • @Reizo no it won't work with overloading, but thats due to using a function pointer, not because of my answer. To enable overloading you can always use a functor instead. Instead of using the function as parameter directly you use a type with overloaded methods – 463035818_is_not_an_ai Oct 07 '20 at 14:24
  • @Elliott yes, maybe my request was not clear enough: I still want to retain the function parameters, but I would like to omit their *types* in the template parameter list (and instead 'deduce' (*big* quotation marks) them from the `f` template function parameter). – Reizo Oct 07 '20 at 14:25
  • 1
    @Reizo, you realise that you can use @idclev's answer like so: `foo(3,'a')`. No need to list the argument types explicitly at the call site. – Elliott Oct 07 '20 at 14:27
  • 1
    @Reizo oh i missed that comment. I falsly assumed that it is obvious that `Args` are deduced, only `f` needs to be specified explicitly. Basically what Elliott said – 463035818_is_not_an_ai Oct 07 '20 at 14:29
  • @idclev463035818 Yes, I realized that in the mean time. It (the deduction of `Args`) does not work for my particular application, where the single function argument type is a `const&` and surprisingly a (non-existing) copy constructor is required, but that's a topic for another research for me :) – Reizo Oct 07 '20 at 14:35
  • @Reizo, perhaps your problem would be solved with perfect forwarding, like in RedFog's answer? ie. `foo(Args && ... args) {return f(std::forward(args)...);}` – Elliott Oct 07 '20 at 14:46
  • @Elliott Turned out that it's ideal to declare `foo` with forward reference argument `(Args&&... args)` (see section **Deduction from a function call** from [here](https://en.cppreference.com/w/cpp/language/template_argument_deduction)). @idclev you might want to update that in your answer, and actually add the `std::forward`. – Reizo Oct 08 '20 at 15:38
  • 1
    @Reizo I wrote "For the sake of brevity I ommitted forwarding and storing th....". To get it fully right even more is needed: https://www.youtube.com/watch?v=I3T4lePH-yA . – 463035818_is_not_an_ai Oct 08 '20 at 15:40
1

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();
}
RedFog
  • 1,005
  • 4
  • 10
  • Quite a complete answer. Any chance that you could get on option that works with overloading? (ie. the function being passed at compile-time is overloaded) – Elliott Oct 07 '20 at 14:58
  • @Elliott sadly, it's impossible. if we want to deduce the parameter types and return type, the function must be not overloaded, otherwise, it's ambiguous anyway. it will be helpful if you consider of it on the side of functional programming and type system. – RedFog Oct 07 '20 at 15:16
  • I can't see why it's ambiguous. If i have `int Bar()` and `char Bar(int)`, then use the proxy function like so: `proxy_func(3)`, then there's enough info. for the compiler to handle the overloading here, so long as we have the right tools to right the `proxy_func` correctly. Wouldn't you agree? – Elliott Oct 07 '20 at 15:21
  • 1
    @Elliott if you pass the argument types, it looks like enough. I said ambiguous that is to deduce the argument types. but in the other hand, we can't use type names declared behind this place. we can't declare the `func` with type that depends on the type names declared later. if you still want, you have to get `Args...` before and then pass `func` and `args...`, just like what I do in C++11. – RedFog Oct 07 '20 at 15:29
  • and there is still a big problem here: the `Args...` must be exactly matched with `Args...` in `Ret(*)(Args...)`, which is hardly satisfied. – RedFog Oct 07 '20 at 15:32
  • Yes, I personally can't find a way to do it, but it feels like it should be possible. Whatever tools the compiler is using to handle the overloading properly just doesn't seem to be available to us in this case. – Elliott Oct 07 '20 at 15:36
  • 1
    oh, my mistake. even if we get the `Args...` before, it's still impossible, because `Ret` can not be deduced before `func` is passed, which conflicts with passing `func` by `Ret(*)(Args...)`. but whatever, I think it's incorrect in semantics, because 2 overloaded `Bar` have 2 different entities and the only same thing between them is their name, which can not be passed now in C++. – RedFog Oct 07 '20 at 15:39
  • if one day, a function has several different types (or in other word, a sum type of these types), it may be possible to deduce its all types or decide what type it is by some inaccurate argument types. just think of a `constexpr std::variant` in compile-time, it is actually able to be deduced. – RedFog Oct 07 '20 at 15:45