Вот мой вопрос. Он не использует рекурсию и расширяет эти кортежи в том же пакете расширения, но она требует немного подготовки:
- Мы строим кортеж ссылок на кортежи, принятых в, 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 в настоящее время нет), так что, возможно, стоит рассмотреть тоже.
В чем проблема с 'станд :: tuple_cat'? – bennofs
Вы можете создать свои собственные версии tuple_size и получить эту работу рекурсивно, тогда реализация multi_apply по существу будет такой же, как и применима. –
Вы пытаетесь оптимизировать продолжительность компиляции? ваши кортежи разных размеров? –