2

Consider I have the following:

void bar(int a, int b)
{
}   

template<typename F, typename... Args>
void foo(F function, Args... args>
{
    function(args...);
}

I would like to have some kind of way to only pass the necessary amount of arguments to the function, so that I would be able to do the following, which should result in a call to bar with 1, 2 as arguments discarding the 3. Without knowing how many arguments the passed in function type F needs.

foo(bar, 1, 2, 3);
foo([](int a, int b){}, 1, 2, 3);

When I try to use the below function traits:

namespace detail
{
    template<typename F, std::size_t... Is, class Tup>
    void call_discard_impl(F&& func, std::index_sequence<Is...>, Tup&& tup) 
    {
        std::forward<F>(func)(std::get<Is>(tup)...);
    }
}

template<typename F, typename... Args>
void call_discard(F&& func, Args&&... args)
{
    detail::call_discard_impl(std::forward<F>(func),
        std::make_index_sequence<function_traits<F>::num_args>{},
        std::forward_as_tuple(args...));
}

I get:

error C2510: 'F': left of '::' must be a class/struct/union
error C2065: '()': undeclared identifier
error C2955: 'function_traits': use of class template requires template argument list

On:

template <typename F>
struct function_traits : public function_traits<decltype(&F::operator())>
{}

I did get the member function version working which did not require the function traits:

namespace detail
{
    template<typename O, typename R, typename... FunArgs, std::size_t... Is, class Tup>
    void call_discard_impl(O* obj, R(O::*mem_func)(FunArgs...), std::index_sequence<Is...>, Tup&& tup)
    {
        ((*obj).*mem_func)(std::get<Is>(tup)...);
    }
}

template<typename O, typename R, typename... FunArgs, typename... Args>
void call_discard(O* obj, R(O::*mem_func)(FunArgs...), Args&&... args)
{
    detail::call_discard_impl(obj, mem_func,
        std::make_index_sequence<sizeof...(FunArgs)>{},
        std::forward_as_tuple(args...));
}
Andreas Loanjoe
  • 2,205
  • 10
  • 26

4 Answers4

3

First, use the following code that lets you find the arity of a lambda or function reference:

template <typename T>
struct function_traits : public function_traits<decltype(&T::operator())>
{};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
{
    using result_type = ReturnType;
    using arg_tuple = std::tuple<Args...>;
    static constexpr auto arity = sizeof...(Args);
};

template <typename R, typename ... Args>
struct function_traits<R(&)(Args...)>
{
    using result_type = R;
    using arg_tuple = std::tuple<Args...>;
    static constexpr auto arity = sizeof...(Args);
};

Next, you forward the variadic arguments along using a tuple pack, and you only expand out to the arity of the function:

template<typename F, std::size_t... Is, class T>
void foo_impl(F && f, std::index_sequence<Is...>, T && tuple) {
    std::forward<F>(f)(std::get<Is>(tuple)...);
}

template<typename F, typename... Args>
void foo(F && f, Args&&... args) {
    foo_impl(std::forward<F>(f),
             std::make_index_sequence<function_traits<F>::arity>{},
             std::forward_as_tuple(args...) );
}

Live example: http://coliru.stacked-crooked.com/a/3ca5df7b55c427b8.

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72
  • I can't get this to work for lambdas because it says the operator() is not defined for type T for function_traits. '()' undeclared identifier – Andreas Loanjoe Sep 03 '17 at 10:30
  • @AndreasLoanjoe Can't reproduce: http://coliru.stacked-crooked.com/a/6c01bab04cae5143. Can you explain? – Nir Friedman Sep 04 '17 at 03:34
2

First, we need a function to retrieve the number or arguments the function requires. This is done using function_traits:

template <class F>
constexpr std::size_t nb_args() {
   return utils::function_traits<F>::arity;
}

And with the help of std::index_sequence, we only dispatch the nb_args<F>() first arguments:

template<typename F, std::size_t... Is, class Tup>
void foo_impl(F && f, std::index_sequence<Is...>, Tup && tup) {
    std::forward<F>(f)( std::get<Is>(tup)... );
}

template<typename F, typename... Args>
void foo(F && f, Args&&... args) {
    foo_impl(std::forward<F>(f),
             std::make_index_sequence<nb_args<F>()>{},
             std::forward_as_tuple(args...) );
}

Demo

O'Neil
  • 3,790
  • 4
  • 16
  • 30
  • No, it will only work for function pointers. Edit: I'm not sure it works for anything. It's using a constexpr function, with a non constexpr argument, in a constexpr context, which is not legal. – Nir Friedman Sep 02 '17 at 13:53
  • @AndreasLoanjoe To work with lambdas, you can use `function_traits` from [here](https://stackoverflow.com/a/7943765/3893262). – O'Neil Sep 02 '17 at 14:18
  • I was looking at this article, but here they can decltype the type of the lambda because the lambda type is known in the scope. But for F I can't decltype like this right? – Andreas Loanjoe Sep 02 '17 at 14:22
  • This answer does not even work for function pointers, does not even compile. – Nir Friedman Sep 02 '17 at 14:28
  • @AndreasLoanjoe You don't have to use decltype, you already have `F`. – O'Neil Sep 02 '17 at 14:49
  • For me it says () is undeclared when doing the decltype(&F::operator()) for lambda type F – Andreas Loanjoe Sep 03 '17 at 10:49
  • @AndreasLoanjoe There is not such `num_args` in the original code of [`function_traits`](https://github.com/kennytm/utils/blob/master/traits.hpp). Did you change something? You just have top copy the file *traits.hpp*. – O'Neil Sep 03 '17 at 14:06
1

Trivial and hardly extensible solution would be to create a wrapper, that will be called with all arguments, but will use only first few of them.

template<typename F, typename... Args>
void foo(F function, Args... args)
{
    // with proper forwarding if needed
    auto lambda = [](auto fnc, auto first, auto second, auto...)
    {
        fnc(first, second);
    };
    lambda(function, args...);
}
Zereges
  • 5,139
  • 1
  • 25
  • 49
0

Here is a solution that will work with anything std::invoke accepts, that invokes the overload with the fewest possible arguments.

template <typename F, typename Args, std::size_t... In>
decltype(auto) invoke_front_impl(F&& f, Args&& args, std::index_sequence<In...>)
{
    if constexpr (std::is_invocable_v<F&&, std::tuple_element_t<In, Args>...>) {
        return std::invoke(std::forward<F>(f), std::get<In>(std::move(args))...);
    } else {
        return invoke_front_impl(
            std::forward<F>(f),
            std::move(args),
            std::make_index_sequence<sizeof...(In) + 1>());
    }
}

template <typename F, typename... Args>
decltype(auto) invoke_front(F&& f, Args&&... args)
{
    return invoke_front_impl(
        std::forward<F>(f),
        std::forward_as_tuple(std::forward<Args>(args)...),
        std::make_index_sequence<0>());
}

Demo on Wandbox

Joseph Thomson
  • 9,888
  • 1
  • 34
  • 38