2017-01-28 10 views
9

std::experimental::apply имеет следующую подпись:Применение нескольких кортежей к той же функции (то есть `применить (F, кортежи ...)`) без рекурсии или `tuple_cat`

template <class F, class Tuple> 
constexpr decltype(auto) apply(F&& f, Tuple&& t); 

Это в основном вызывающую f путем расширения t как аргументы.


Я хотел бы что-то, что делает одно и то же, но с несколькими кортежей одновременно:

template <class F, class... Tuples> 
constexpr decltype(auto) multi_apply(F&& f, Tuples&&... ts); 

Пример использования:

std::tuple t0{1, 2, 3}; 
std::tuple t1{4, 5, 6}; 
auto sum = [](auto... xs){ return (0 + ... + xs); }; 

assert(multi_apply(sum, t0, t1) == 1 + 2 + 3 + 4 + 5 + 6); 

Я могу думать различного наивного способа осуществления multi_apply:

  • Используйте std::tuple_cat, а затем позвоните по телефону std::experimental::apply.

  • Используйте рекурсию для привязки аргументов каждого кортежа к серии лямбда, которые в конечном итоге вызывают исходную функцию.

Но я спрашиваю: , как я могу реализовать multi_apply, не прибегая к std::tuple_cat или рекурсии?

То, что я в идеале хотел бы сделать это: генерировать std::index_sequence для каждого кортежа и сопоставить каждый кортеж со своей собственной индексной последовательностью в том же VARIADIC расширения. Это возможно? Пример:

// pseudocode-ish 
template <class F, std::size_t... Idxs, class... Tuples> 
constexpr decltype(auto) multi_apply_helper(
    F&& f, std::index_sequence<Idxs>... seqs, Tuples&&... ts) 
{ 
    return f(std::get<Idxs>(ts)...); 
} 
+4

В чем проблема с 'станд :: tuple_cat'? – bennofs

+1

Вы можете создать свои собственные версии tuple_size и получить эту работу рекурсивно, тогда реализация multi_apply по существу будет такой же, как и применима. –

+0

Вы пытаетесь оптимизировать продолжительность компиляции? ваши кортежи разных размеров? –

ответ

10

Вот мой вопрос. Он не использует рекурсию и расширяет эти кортежи в том же пакете расширения, но она требует немного подготовки:

  • Мы строим кортеж ссылок на кортежи, принятых в, RValue ссылки на аргументы RValue, именующее выражение ссылки для аргументов lvalue, чтобы иметь надлежащую пересылку в последнем вызове (именно то, что делает std::forward_as_tuple, как указано в комментариях TC). Кортеж построен и передается как rvalue, поэтому сведение ссылок обеспечивает правильные категории значений для каждого аргумента при последнем вызове f.
  • Мы строим два приглаженные последовательности индексов, как размер, равный сумме всех размеров кортежей:
    • Внешние индексы выбрать кортеж, поэтому они повторяют то же значение (индекс кортежа в наборе упаковке) в количество раз равное размеру каждого кортежа.
    • Внутренние элементы выбирают элемент в каждом кортеже, поэтому они увеличиваются с 0 до уровня, меньшего размера кортежа для каждого кортежа.

После того, как у нас, что на месте, мы просто расширить оба индекса последовательности в вызове f.

#include <tuple> 
#include <array> 
#include <cstddef> 
#include <utility> 
#include <type_traits> 
#include <iostream> 

template<std::size_t S, class... Ts> constexpr auto make_indices() 
{ 
    constexpr std::size_t sizes[] = {std::tuple_size_v<std::remove_reference_t<Ts>>...}; 
    using arr_t = std::array<std::size_t, S>; 
    std::pair<arr_t, arr_t> ret{}; 
    for(std::size_t c = 0, i = 0; i < sizeof...(Ts); ++i) 
     for(std::size_t j = 0; j < sizes[i]; ++j, ++c) 
     { 
     ret.first[c] = i; 
     ret.second[c] = j; 
     } 
    return ret; 
} 

template<class F, class... Tuples, std::size_t... OuterIs, std::size_t... InnerIs> 
constexpr decltype(auto) multi_apply_imp_2(std::index_sequence<OuterIs...>, std::index_sequence<InnerIs...>, 
              F&& f, std::tuple<Tuples...>&& t) 
{ 
    return std::forward<F>(f)(std::get<InnerIs>(std::get<OuterIs>(std::move(t)))...); 
} 

template<class F, class... Tuples, std::size_t... Is> 
constexpr decltype(auto) multi_apply_imp_1(std::index_sequence<Is...>, 
              F&& f, std::tuple<Tuples...>&& t) 
{ 
    constexpr auto indices = make_indices<sizeof...(Is), Tuples...>(); 
    return multi_apply_imp_2(std::index_sequence<indices.first[Is]...>{}, std::index_sequence<indices.second[Is]...>{}, 
     std::forward<F>(f), std::move(t)); 
} 

template<class F, class... Tuples> 
constexpr decltype(auto) multi_apply(F&& f, Tuples&&... ts) 
{ 
    constexpr std::size_t flat_s = (0U + ... + std::tuple_size_v<std::remove_reference_t<Tuples>>); 
    if constexpr(flat_s != 0) 
     return multi_apply_imp_1(std::make_index_sequence<flat_s>{}, 
     std::forward<F>(f), std::forward_as_tuple(std::forward<Tuples>(ts)...)); 
    else 
     return std::forward<F>(f)(); 
} 

int main() 
{ 
    auto t0 = std::make_tuple(1, 2); 
    auto t1 = std::make_tuple(3, 6, 4, 5); 
    auto sum = [](auto... xs) { return (0 + ... + xs); }; 

    std::cout << multi_apply(sum, t0, t1, std::make_tuple(7)) << '\n'; 
} 

Он компилируется в версиях соединительных линий Clang и GCC в режиме C++ 1z. С точки зрения генерируемого кода, GCC, с -O2 оптимизирует вызов multi_apply к постоянной 28.


Замена std::array со встроенным в массив внутри make_indices по using arr_t = std::size_t[S]; делает его компиляции на Clang 3.9.1 (эта версия LIBC++ не хватает constexpr на std::array «s operator[]).

Далее замена std::tuple_size_v с std::tuple_size<X>::value и удалением тест if constexpr в multi_apply делает его компиляции на GCC 6.3.0. (Тест обрабатывает случаи, когда никаких кортежей не передаются или все кортежи, передаваемые в пустых.)

Далее заменяя использование кратных выражений с вызовами, как

sum_array({std::tuple_size_v<std::remove_reference_t<Tuples>>...}) 

где sum_array может быть что-то простое, как

template<class T, std::size_t S> constexpr T sum_array(const T (& a)[S], std::size_t i = 0) 
{ 
    return i < S ? a[i] + sum_array(a, i + 1) : 0; 
} 

делает его компиляцией на последнем MSVC 2017 RC (MSVC фактически имеет std::tuple_size_v, но ему нужны другие изменения). Сгенерированный код по-прежнему велик: после замены тела sum лямбда на sum_array({xs...}), полученный код является прямым вызовом sum_array с массивом, встроенным непосредственно из элементов всех кортежей, поэтому оборудование multi_apply не вводит любые накладные расходы времени выполнения.


std::apply определяется в терминах Invoke, так, чтобы держать вещи последовательно, окончательный вызов f должен быть

std::invoke(std::forward<F>(f), std::get<InnerIs>(std::get<OuterIs>(std::move(t)))...) 

Реализации могут предоставлять noexcept-спецификатор на std::apply (по крайней мере, , libC++ делает: libstdC++ и MSVC в настоящее время нет), так что, возможно, стоит рассмотреть тоже.

+0

'std :: tuple {std :: forward (ts) ...}' это просто 'forward_as_tuple', нет? –

+2

@ T.C. Совершенно верно. Просто посмотрите на мое новое заново изобретенное колесо ... это похоже на другое, но это мое *. – bogdan

+0

Отличный ответ. Именно то, что я надеялся увидеть! :) –