Вызов функции с частью переменных аргументов

Считайте, что у меня есть следующее:

void bar(int a, int b)
{
}   

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

Я хотел бы иметь какой-то способ передать функции только необходимое количество аргументов, чтобы я мог сделать следующее, что должно привести к вызову bar с 1, 2 в качестве аргументов, отбрасывая 3. Не зная, сколько аргументов нужно передать функции типа F.

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

Когда я пытаюсь использовать следующие функции:

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

Я получил:

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())>
{}

У меня работала версия функции-члена, которая не требовала признаков функции:

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

person Andreas Loanjoe    schedule 02.09.2017    source источник


Ответы (4)


Во-первых, нам нужна функция для получения числа или аргументов, которые требуются функции. Это делается с помощью function_traits:

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

А с помощью std::index_sequence мы отправляем только nb_args<F>() первых аргументов:

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

Демо

person O'Neil    schedule 02.09.2017
comment
Нет, это будет работать только для указателей на функции. Изменить: я не уверен, что это работает для чего-либо. Он использует функцию constexpr с аргументом, отличным от constexpr, в контексте constexpr, что недопустимо. - person Nir Friedman; 02.09.2017
comment
@AndreasLoanjoe Для работы с лямбда-выражениями вы можете использовать function_traits из здесь. - person O'Neil; 02.09.2017
comment
Я просматривал эту статью, но здесь они могут указать тип лямбда, потому что тип лямбда известен в области. Но для F я не могу так декламировать, верно? - person Andreas Loanjoe; 02.09.2017
comment
Этот ответ даже не работает для указателей на функции, даже не компилируется. - person Nir Friedman; 02.09.2017
comment
@AndreasLoanjoe Вам не нужно использовать decltype, у вас уже есть F. - person O'Neil; 02.09.2017
comment
Для меня это говорит () не объявлено при выполнении decltype(&F::operator()) для лямбда-типа F - person Andreas Loanjoe; 03.09.2017
comment
@AndreasLoanjoe В исходном коде function_traits такого num_args нет. Вы что-то изменили? Вам просто нужно скопировать файл traits.hpp. - person O'Neil; 03.09.2017

Во-первых, используйте следующий код, который позволяет найти арность лямбда-выражения или ссылки на функцию:

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);
};

Затем вы пересылаете аргументы с переменным числом аргументов, используя пакет кортежей, и расширяетесь только до арности функции:

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

Живой пример: http://coliru.stacked-crooked.com/a/3ca5df7b55c427b8 .

person Nir Friedman    schedule 02.09.2017
comment
Я не могу заставить это работать для лямбда-выражений, потому что он говорит, что оператор() не определен для типа T для function_traits‹F›. '()' необъявленный идентификатор - person Andreas Loanjoe; 03.09.2017
comment
@AndreasLoanjoe Не удается воспроизвести: coliru.stacked-crooked.com/a/6c01bab04cae5143 . Вы можете объяснить? - person Nir Friedman; 04.09.2017

Тривиальным и трудно расширяемым решением будет создание обертки, которая будет вызываться со всеми аргументами, но использовать только первые несколько из них.

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...);
}
person Zereges    schedule 02.09.2017

Вот решение, которое будет работать со всем, что принимает std::invoke и вызывает перегрузка с наименьшим количеством возможных аргументов.

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>());
}

Демо на Wandbox

person Joseph Thomson    schedule 12.11.2019