【问题标题】:Expand two parameter packs展开两个参数包
【发布时间】:2018-03-04 01:32:32
【问题描述】:

考虑以下代码:

static constexpr size_t Num {2};
struct S {
    std::array<size_t, Num> get () { return {1, 2}; }
};

struct S1 : S {};
struct S2 : S {};

struct M {
    template <typename T>
    typename std::enable_if<std::is_same<T, S1>::value, S1>::type get () const { 
        return S1 {}; 
    }

    template <typename T>
    typename std::enable_if<std::is_same<T, S2>::value, S2>::type get () const { 
        return S2 {}; 
    }
};

我想要一个函数,将两个或多个std::arrays 合并为一个std::array

到目前为止,我以这样的方式结束:

template <typename Mode, typename... Rs, size_t... Ns>
std::array<size_t, sizeof... (Rs)*Num> get_array (const Mode& mode, Sequence::Sequence<Ns...>) {
    return {std::get<Ns> (mode.template get<Rs...> ().get ())...};
}

我想要下面的代码

M m;
auto x = get_array<M, S1, S2> (m, Sequence::Make<2> {});

产生std::array&lt;size_t, 4&gt; 填充{1, 2, 1, 2}

其中Sequence::SequenceSequence::Makedescribed here

我知道在这种情况下放置 Rs... 是不正确的(如果 sizeof... (Rs) 为 1 则很好,返回 std::array&lt;size_t, 2&gt;{1, 2})但我不知道放在哪里进行如下所示的扩展:

std::get<0> (mode.template get<Rs[0]> ().get ()), 
std::get<1> (mode.template get<Rs[0]> ().get ()),
std::get<0> (mode.template get<Rs[1]> ().get ()), 
std::get<1> (mode.template get<Rs[1]> ().get ());

当然Rs[0] 我的意思是参数包中的第一个类型。

有可能吗?

【问题讨论】:

  • 严格的 C++11 解决方案?没有 C++14?
  • @AndyG 不幸的是,在我需要这个的地方我只能访问 C++11 编译器,可悲但真实

标签: c++ c++11


【解决方案1】:

假设我们使用Xeo's index sequence 实现,我们可以这样做:

首先创建一个用于连接两个数组的函数。它接收数组,加上每个数组的索引序列(detail::seqindex_sequence 类型)

template<class T, size_t N, size_t M, size_t... I, size_t... J>
std::array<T, N + M> concat(const std::array<T, N>& arr1, const std::array<T, M>& arr2, detail::seq<I...>, detail::seq<J...>)
{
     return {arr1[I]..., arr2[J]...};
}

接下来,从您的 get_array 函数中调用此函数,但我们将从 main 中的调用中收到的 seq 加倍:

template<class MODE, class... T, size_t... I>
auto get_array(MODE m, detail::seq<I...>) ->decltype(concat(m.template get<T>().get()..., detail::seq<I...>{}, detail::seq<I...>{})){
    return concat(m.template get<T>().get()..., detail::seq<I...>{}, detail::seq<I...>{});
}

main 中的调用看起来就像在您的代码中一样:

M m;
auto x = get_array<M, S1, S2>(m, detail::gen_seq<2>{});

detail::gen_seq 是 Xeo 的 make_index_sequence 的实现。

Live Demo

请注意,我在 Xeo 的索引序列 impl 中将 unsigned 替换为 size_t

在 C++14 中,我们不需要实现 seqgen_seq,我们也不需要在函数后面加上 -&gt; decltype()

在 C++17 中,使用折叠表达式更容易将我们的串联泛化为任意数量的数组。

【讨论】:

  • 我怀疑用户已经意识到了这一点,但它涉及到默认构造然后复制,对于非平凡的类型不太好,并且需要默认构造,这应该是不必要的。
  • @NirFriedman:我明白了......所以我想我们需要像this 这样的东西,只是我们需要像 OP 一样在 C++11 中复制 index_sequence
  • @NirFriedman:哦,我看到你打败了我 :-) 不错的代码。我会支持你的并删除我的。
  • 不,你有一些更相关的细节,不要删除它!把两者都留着:-)。
  • @AndyG 谢谢,该解决方案有效,但仅适用于两个阵列,对吧?我还需要更多的数组参与。
【解决方案2】:

是的,这可以通过标准的index_sequence 技巧来完成:

template <class T, std::size_t N1, std::size_t N2, std::size_t ... Is, std::size_t ... Js>
std::array<T, N1 + N2> merge_impl(const std::array<T, N1>& a1,
                                  const std::array<T, N2>& a2, 
                                  std::index_sequence<Is...>,
                                  std::index_sequence<Js...>) {
    return {a1[Is]..., a2[Js]...};
}

template <class T, std::size_t N1, std::size_t N2>
std::array<T, N1 + N2> merge(const std::array<T, N1>& a1, const std::array<T, N2>& a2) {
    return merge_impl(a1, a2,
                      std::make_index_sequence<N1>{}, 
                      std::make_index_sequence<N2>{});
}

index_sequence只在14标准中,但在11中可以轻松实现;有很多资源(包括 SO)描述了如何做到这一点(编辑:它基本上等同于你的 Sequence 东西,不妨习惯它们的标准名称)。实例:http://coliru.stacked-crooked.com/a/54dce4a695357359

【讨论】:

  • 很好,不过还是合并两个数组,合并任意数量的数组呢?
【解决方案3】:

首先,这基本上是要求连接任意数量的数组。这与连接任意数量的元组非常相似,其中有一个标准库函数,即使在 C++11 中也是如此:std::tuple_cat()。这让我们快到了:

template <class... Ts, class M>
auto get_array(M m) -> decltype(std::tuple_cat(m.template get<Ts>()...)) {
    return std::tuple_cat(m.template get<Ts>()...);
}

请注意,我翻转了模板参数,所以这只是get_array&lt;T1, T2&gt;(m) 而不必写get_array&lt;M, T1, T2&gt;(m)

现在的问题是,我们如何写array_cat?我们将只使用tuple_cat 并将生成的tuple 转换为array。假设index_sequence 的实现是可用的(无论如何,这都是你想要在你的集合中的东西):

template <class T, class... Ts, size_t... Is>
std::array<T, sizeof...(Ts)+1> to_array_impl(std::tuple<T, Ts...>&& tup,
                                             std::index_sequence<Is...> ) {
    return {{std::get<Is>(std::move(tup))...}};
}

template <class T, class... Ts>
std::array<T, sizeof...(Ts)+1> to_array(std::tuple<T, Ts...>&& tup) {
    return to_array_impl(std::move(tup), std::index_sequence_for<T, Ts...>());
}

template <class... Tuples>
auto array_cat(Tuples&&... tuples) -> decltype(to_array(std::tuple_cat(std::forward<Tuples>(tuples)...))) {
    return to_array(std::tuple_cat(std::forward<Tuples>(tuples)...));
}

这给了你:

template <class... Ts, class M>
auto get_array(M m) -> decltype(array_cat(m.template get<Ts>()...)) {
    return array_cat(m.template get<Ts>()...);
}

它可以处理任意多种类型。

【讨论】:

  • tuple_cat 允许但不需要处理非std::tuple 类似元组的类型,例如std::array
  • 谢谢,尽管我认为其他解决方案比你的更容易,但这个解决方案适用于任意数量的数组,所以这就是我想要的。
  • Barry,你知道 tuple_cat 是否纯粹就地创建新元组? IE。它不创建任何中间元组?如果是这样,我需要看看它是如何做到的。我想不出我将如何传递可变数量的整数序列包......
  • @NirFriedman 我有给你的博文吗! ericniebler.com/2014/11/13/tiny-metaprogramming-library
  • @Barry Brilliant!谢谢。
【解决方案4】:

所以这里是任意数量的同类型数组。我们基本上实现了tuple_cat 的高度限制版本,由于数组中的元素数量相同,因此变得更加容易。我使用了几个 C++14 和 17 库特性,这些特性都可以在 C++11 中轻松实现。

template<class, size_t> struct div_sequence;
template<size_t...Is, size_t Divisor>
struct div_sequence<std::index_sequence<Is...>, Divisor>
{
   using quot = std::index_sequence<Is / Divisor...>;
   using rem = std::index_sequence<Is % Divisor...>;
};


template<class T, size_t...Ns, size_t...Is, class ToA>
std::array<T, sizeof...(Ns)> array_cat_impl(std::index_sequence<Ns...>,
                                            std::index_sequence<Is...>,
                                            ToA&& t) 
{
    // NB: get gives you perfect forwarding; [] doesn't.
    return {std::get<Is>(std::get<Ns>(std::forward<ToA>(t)))... }; 
}

template<class Array, class... Arrays,
         class VT = typename std::decay_t<Array>::value_type,
         size_t S = std::tuple_size<std::decay_t<Array>>::value,
         size_t N = S * (1 + sizeof...(Arrays))>
std::array<VT, N> array_cat(Array&& a1, Arrays&&... as) 
{
     static_assert(std::conjunction_v<std::is_same<std::decay_t<Array>,
                                                   std::decay_t<Arrays>>...
                                      >, "Array type mismatch");

     using ind_seq = typename div_sequence<std::make_index_sequence<N>, S>::rem;
     using arr_seq = typename div_sequence<std::make_index_sequence<N>, S>::quot;
     return array_cat_impl<VT>(arr_seq(), ind_seq(), 
                               std::forward_as_tuple(std::forward<Array>(a1),
                                                     std::forward<Arrays>(as)...)
                               );
}

我们还可以重用tuple_cat 机器,就像@Barry 的回答一样。为了回避潜在的 QoI 问题,避免依赖扩展和额外的移动,我们不想直接tuple_catstd::arrays。相反,我们首先将数组转换为引用元组。

template<class TupleLike, size_t... Is>
auto as_tuple_ref(TupleLike&& t, std::index_sequence<Is...>)
    -> decltype(std::forward_as_tuple(std::get<Is>(std::forward<TupleLike>(t))...))
{
    return std::forward_as_tuple(std::get<Is>(std::forward<TupleLike>(t))...);
}

template<class TupleLike,
         size_t S = std::tuple_size<std::decay_t<TupleLike>>::value >
auto as_tuple_ref(TupleLike&& t)
    -> decltype(as_tuple_ref(std::forward<TupleLike>(t), std::make_index_sequence<S>()))
{
    return as_tuple_ref(std::forward<TupleLike>(t), std::make_index_sequence<S>());
}

然后我们可以将tuple_cat'd 引用转换回一个数组:

template <class R1, class...Rs, size_t... Is>
std::array<std::decay_t<R1>, sizeof...(Is)> 
   to_array(std::tuple<R1, Rs...> t, std::index_sequence<Is...>) 
{
   return { std::get<Is>(std::move(t))... };
}

template <class R1, class...Rs>
std::array<std::decay_t<R1>, sizeof...(Rs) + 1> to_array(std::tuple<R1, Rs...> t) 
{
   static_assert(std::conjunction_v<std::is_same<std::decay_t<R1>, std::decay_t<Rs>>...>,
                 "Array element type mismatch");
   return to_array(t, std::make_index_sequence<sizeof...(Rs) + 1>());
}

最后,array_cat 本身只是

template <class... Arrays>
auto array_cat(Arrays&&... arrays) 
    -> decltype(to_array(std::tuple_cat(as_tuple_ref(std::forward<Arrays>(arrays))...))) 
{
    return to_array(std::tuple_cat(as_tuple_ref(std::forward<Arrays>(arrays))...));
}

任何体面的优化器都应该毫不费力地优化中间引用元组。

【讨论】:

  • 这很酷,我希望我想从中了解一些东西:P。无论如何,也许对于一些更了解所有这些模板魔术诗的人来说,这会有所帮助:)
猜你喜欢
  • 2017-06-22
  • 2022-07-05
  • 2015-06-06
  • 2014-09-25
  • 1970-01-01
  • 1970-01-01
  • 2017-01-10
  • 2022-01-08
  • 1970-01-01
相关资源
最近更新 更多