Vit*_*meo 10 c++ templates tuples variadic-templates c++17
std::experimental::apply
有以下签名:
template <class F, class Tuple>
constexpr decltype(auto) apply(F&& f, Tuple&& t);
Run Code Online (Sandbox Code Playgroud)
它基本上f
通过扩展t
元素作为参数来调用.
我想要的东西完全相同,但同时有多个元组:
template <class F, class... Tuples>
constexpr decltype(auto) multi_apply(F&& f, Tuples&&... ts);
Run Code Online (Sandbox Code Playgroud)
用法示例:
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);
Run Code Online (Sandbox Code Playgroud)
我可以想到各种天真的实施方式multi_apply
:
使用std::tuple_cat
然后调用std::experimental::apply
.
使用递归将每个元组的参数绑定到一系列最终调用原始函数的lambda.
但我要问的是:如何在multi_apply
不诉诸std::tuple_cat
或递归的情况下实施?
理想情况下,我想做的是:std::index_sequence
为每个元组生成一个元组,并在同一个可变扩展中将每个元组与自己的索引序列进行匹配.这可能吗?例:
// 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)...);
}
Run Code Online (Sandbox Code Playgroud)
bog*_*dan 11
这是我的看法.它不使用递归,它在同一个包扩展中扩展了这些元组,但它需要一些准备:
std::forward_as_tuple
正如TC中注释的那样).元组是作为右值构建和传递的,因此引用折叠可确保最终调用中每个参数的正确值类别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';
}
Run Code Online (Sandbox Code Playgroud)
它在C++ 1z模式下编译Clang和GCC的主干版本.就生成的代码而言,GCC -O2
优化了multi_apply
对常量的调用28
.
更换std::array
内部具有内置的阵列make_indices
通过using arr_t = std::size_t[S];
使得汇编锵3.9.1(的libc ++该版本缺乏constexpr
上std::array
的operator[]
).
进一步替换std::tuple_size_v
与std::tuple_size<X>::value
和除去if constexpr
试验中multi_apply
使得它汇编GCC 6.3.0.(测试处理没有传入元组或传入的所有元组都为空的情况.)
进一步用如调用替换fold表达式的用法
sum_array({std::tuple_size_v<std::remove_reference_t<Tuples>>...})
Run Code Online (Sandbox Code Playgroud)
哪里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;
}
Run Code Online (Sandbox Code Playgroud)
使它在最新的MSVC 2017 RC上编译(MSVC实际上有std::tuple_size_v
,但它需要其他更改).生成的代码仍然很好:在替换sum
lambda 的主体之后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)))...)
Run Code Online (Sandbox Code Playgroud)
实现可能会提供一个noexcept-specifier std::apply
(至少libc ++会这样; libstdc ++和MSVC目前没有),因此也值得考虑.
归档时间: |
|
查看次数: |
565 次 |
最近记录: |