В чем разница между std :: invoke и std :: apply? - PullRequest
0 голосов
/ 21 сентября 2018

Они оба используются в качестве общего метода вызова функций, функций-членов и вообще всего, что можно вызвать.Из cppreference единственное реальное отличие, которое я вижу, состоит в том, что в std::invoke параметры функции (сколько бы их ни было) forward передаются в функцию, тогда как в std::apply параметры передаются как tuple.Это действительно единственная разница?Зачем им создавать отдельную функцию только для обработки tuple с?

Ответы [ 2 ]

0 голосов
/ 22 сентября 2018

Вы используете std::apply, потому что:

1: Реализация apply, даже если у вас есть доступ к std::invoke, является большой болью.Превращение кортежа в пакет параметров не является тривиальной операцией.Реализация apply будет выглядеть примерно так (из cppref):

namespace detail {
template <class F, class Tuple, std::size_t... I>
constexpr decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>)
{
    return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...);
}
}  // namespace detail

template <class F, class Tuple>
constexpr decltype(auto) apply(F&& f, Tuple&& t)
{
    return detail::apply_impl(
        std::forward<F>(f), std::forward<Tuple>(t),
        std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
}

Конечно, это не самый сложный код в мире для написания, но он также не совсем тривиален.Особенно, если вы не используете трюк метапрограммирования index_sequence.

2: потому что вызов функции путем распаковки элементов tuple довольно полезен.Базовая операция, которую он поддерживает, - это возможность упаковать набор аргументов, передать их и затем вызвать функцию с этими параметрами.Технически у нас уже есть возможность сделать это с одним параметром (передавая значение вокруг), но через apply вы получаете возможность сделать это с несколькими параметрами.

Это также позволяет вам делатьтрюки метапрограммирования, такие как метапрограммное выполнение маршаллинга между языками.Вы регистрируете функцию в такой системе, которой дается подпись функции (и самой функции).Эта подпись используется для маршалинга данных посредством метапрограммирования.

Когда другой язык вызывает вашу функцию, генерируемая метапрограммой функция просматривает список типов параметров и извлекает значения из другого языка на основе этих типов.,Из чего он их извлекает?Некоторая структура данных, которая содержит значения.А поскольку метапрограммирование не может (легко) создать struct/class, вы вместо этого строите tuple (действительно, поддержка метапрограммирования, подобная этой, составляет 80% от того, почему существует tuple).

Как только tuple<Params>встроенный, вы используете std::apply для вызова функции.Вы действительно не можете сделать это с invoke.

3: Вы не хотите превращать все параметры в tuple, чтобы иметь возможность выполнить эквивалент invoke.

4: Вам необходимо установить разницу между invoke функцией, которая требует tuple, и apply, чтобы распаковать tuple.В конце концов, если вы пишете шаблонную функцию, которая выполняет invoke для параметров, указанных пользователем, было бы ужасно, если бы пользователь просто предоставил один tuple в качестве аргумента, а ваша функция invoke была распакована.it.

Вы можете использовать другие средства для дифференциации случаев, но наличие различных функций является адекватным решением для простого случая.Если бы вы писали более обобщенную функцию apply -стиля, где вы хотите иметь возможность распаковывать tuple s в дополнение к передаче других аргументов или распаковывать несколько кортежей в списки аргументов (или их комбинацию), вы быЯ хочу иметь специальный super_invoke, который мог бы справиться с этим.

Но invoke - простая функция для простых нужд.То же самое касается apply.

0 голосов
/ 21 сентября 2018

Разве это единственная разница?Зачем им создавать отдельную функцию только для обработки кортежей?

Потому что вам действительно нужны оба варианта, поскольку они делают разные вещи.Рассмотрим:

int f(int, int);
int g(tuple<int, int>);

tuple<int, int> tup(1, 2);

invoke(f, 1, 2); // calls f(1, 2)
invoke(g, tup);  // calls g(tup)
apply(f, tup);   // also calls f(1, 2)

Обратите особое внимание на разницу между invoke(g, tup), который не распаковывает tuple, и apply(f, tup), что делает.Иногда вам нужно и то, и другое.

Вы правы, что обычно это очень тесно связанные операции.Действительно, Мэтт Калабрезе пишет библиотеку с именем Argot , которая объединяет обе операции, и вы различаете их не по функции, которую вы вызываете, а по тому, как вы декорируете аргументы:

call(f, 1, 2);         // f(1,2)
call(g, tup);          // g(tup)
call(f, unpack(tup));  // f(1, 2), similar to python's f(*tup)
...