How to Deduce Argument List from Function Pointer?

Viewed 1958

Given two or more example functions, is it possible to write templated code which would be able to deduce the arguments of a function provided as a template parameter?

This is the motivating example:

void do_something(int value, double amount) {
    std::cout << (value * amount) << std::endl;
}

void do_something_else(std::string const& first, double & second, int third) {
    for(char c : first) 
        if(third / c == 0) 
            second += 13.7;
}

template<void(*Func)(/*???*/)>
struct wrapper {
    using Args = /*???*/;
    void operator()(Args&& ... args) const {
        Func(std::forward<Args>(args)...);
    }
};

int main() {
    wrapper<do_something> obj; //Should be able to deduce Args to be [int, double]
    obj(5, 17.4); //Would call do_something(5, 17.4);
    wrapper<do_something_else> obj2; //Should be able to deduce Args to be [std::string const&, double&, int]
    double value = 5;
    obj2("Hello there!", value, 70); //Would call do_something_else("Hello there!", value, 70);
}

In both uses of /*???*/, I am trying to work out what I could put there that would enable this kind of code.

The following doesn't appear to work, due to Args not being defined before its first use (along with what I have to assume are numerous syntax errors besides), and even if it did, I'm still looking for a version that doesn't require explicit writing of the types themselves:

template<void(*Func)(Args ...), typename ... Args)
struct wrapper {
    void operator()(Args ...args) const {
        Func(std::forward<Args>(args)...);
    }
};

wrapper<do_something, int, double> obj;
4 Answers

With C++17 we can have auto template non-type parameters which make possible the Wrapper<do_something> w{} syntax 1).

As for deducing Args... you can do that with a specialization.

template <auto* F>
struct Wrapper {};

template <class Ret, class... Args, auto (*F)(Args...) -> Ret>
struct Wrapper<F>
{
    auto operator()(Args... args) const
    {
        return F(args...);
    }
};
Wrapper<do_something> w{};
w(10, 11.11);

1) Without C++17 it's impossible to have the Wrapper<do_something> w{} nice syntax.

The best you can do is:

template <class F, F* func>
struct Wrapper {};

template <class Ret, class... Args, auto (*F)(Args...) -> Ret>
struct Wrapper<Ret (Args...), F>
{
    auto operator()(Args... args) const
    {
        return F(args...);
    }
};
Wrapper<declype(do_something), do_something> w{};

With C++17, you can do this:

template <auto FUNC, typename = decltype(FUNC)>
struct wrapper;

template <auto FUNC, typename RETURN, typename ...ARGS>
struct wrapper<FUNC, RETURN (*)(ARGS...)> {
    RETURN operator()(ARGS ...args) {
        return FUNC(args...);
    }
};

I've learned this technique from W.F.'s answer

Further improvement of C++17 version: less template parameters and proper noexcept annotation:

template<auto VFnPtr> struct
wrapper;

template<typename TResult, typename... TArgs, TResult ( * VFnPtr)(TArgs...)> struct
wrapper<VFnPtr>
{
    TResult
    operator ()(TArgs... args) const noexcept(noexcept((*VFnPtr)(::std::forward<TArgs>(args)...)))
    {
        return (*VFnPtr)(::std::forward<TArgs>(args)...);
    }
};

With C++11 you can consider a templated make_wrapper helper function. However, with this approach the function pointer is not a template parameter. Instead, the function pointer is "carried" by the non-static data member called f_ in the following example:

#include <iostream>

void do_something(int value, double amount) {
  std::cout << (value * amount) << std::endl;
}

void do_something_else(std::string const& first, double & second, int third) {
  for(char c : first)
    if(third / c == 0)
      second += 13.7;
}

template<class Ret, class... Args>
using function_pointer = Ret(*)(Args...);

template<class Ret, class... Args>
struct wrapper {
  using F = function_pointer<Ret, Args...>;

  F f_;

  explicit constexpr wrapper(F f) noexcept : f_{f} {}

  template<class... PreciseArgs>// not sure if this is required
  Ret operator()(PreciseArgs&&... precise_args) const {
    return f_(std::forward<PreciseArgs>(precise_args)...);
  }
};

template<class Ret, class... Args>
constexpr auto make_wrapper(
  function_pointer<Ret, Args...> f
) -> wrapper<Ret, Args...> {
  return wrapper<Ret, Args...>(f);
}

int main() {
  constexpr auto obj = make_wrapper(do_something);
  obj(5, 17.4);
  constexpr auto obj2 = make_wrapper(do_something_else);
  double value = 5;
  obj2("Hello there!", value, 70);

  return 0;
}
Related