【问题标题】:split variadic template arguments拆分可变参数模板参数
【发布时间】:2011-07-25 23:45:51
【问题描述】:

如何将可变参数模板参数分成两半?比如:

template <int d> struct a {
  std::array <int, d> p, q;
  template <typename ... T> a (T ... t) : p ({half of t...}), q ({other half of t...}) {} 
};

【问题讨论】:

  • 有趣的问题。我想,由于您可能可以将 args 拆分为元组,因此可以从以下开始:ideone.com/YyeNC。遗憾的是,构造函数的多个参数包是非法的,尽管在这种情况下应该清楚每个包中的类型。

标签: c++ c++11 variadic-templates


【解决方案1】:

我们仍然缺乏很多帮助来操作可变参数包(或者我不知道它们)。在一个不错的 Boost 库将它们带给我们之前,我们仍然可以编写自己的。

例如,如果您愿意将数组的初始化推迟到构造函数主体,您可以创建并使用将部分参数包复制到输出迭代器的函数:

#include <array>
#include <cassert>
#include <iostream>

// Copy n values from the parameter pack to an output iterator
template < typename OutputIterator >
void copy_n( size_t n, OutputIterator )
{
  assert ( n == 0 );
}

template < typename OutputIterator, typename T, typename... Args >
void copy_n( size_t n, OutputIterator out, const T & value, Args... args )
{
  if ( n > 0 )
  {
    *out = value;
    copy_n( n - 1, ++out, args... );
  }
}

// Copy n values from the parameter pack to an output iterator, starting at
// the "beginth" element
template < typename OutputIterator >
void copy_range( size_t begin, size_t size, OutputIterator out )
{
  assert( size == 0 );
}


template < typename OutputIterator, typename T, typename... Args >
void copy_range( size_t begin, size_t size, OutputIterator out, T value, Args... args )
{
  if ( begin == 0 )
  {
    copy_n( size, out, value, args... );
  }
  else
  {
    copy_range( begin - 1, size, out, args... );
  }
}


template < int N > 
struct DoubleArray
{
  std::array< int, N > p;
  std::array< int, N > q;

  template < typename... Args >
  DoubleArray ( Args... args )
  {
    copy_range( 0, N, p.begin(), args... );
    copy_range( N, N, q.begin(), args... );
  } 

};

int main()
{
  DoubleArray<3> mya(1, 2, 3, 4, 5, 6);
  std::cout << mya.p[0] << mya.p[2] << std::endl;  // 13
  std::cout << mya.q[0] << mya.q[2] << std::endl;  // 46
}

如您所见,您可以(并非如此)轻松地创建自己的算法来操作参数包;所需要的只是对递归和模式匹配有很好的理解(在进行模板元编程时总是如此)。

【讨论】:

  • 这样的boost库已经存在:boost.fusion。如果将参数包转换为tuple,则可以使用 boost.fusion 将tuple 拆分为两个单独的tuples 或boost::fusion::vectors。我会编写一个演示代码,但目前我手头只有 VC++,它缺少可变参数模板参数。
  • 我知道 Boost.Fusion 库,但我正在考虑一个可以处理参数包的递归性质的库:我们应该能够操作参数包而不将其转换为首先是元组,这需要不必要的计算。然而,我给出的代码确实可以通过使用 Fusion 来简化(也许我会发布另一个答案来说明如何做到这一点)。
  • 使用initializer_list简化代码:Demo
【解决方案2】:

Luc 的解决方案简洁明了,但非常缺乏乐趣。
因为只有一种使用可变参数模板的正确方法,那就是滥用它们来做疯狂的过度复杂的元编程工作:)

像这样:

template <class T, size_t... Indx, class... Ts>
std::array<T, sizeof...(Indx)>
split_array_range_imp(pack_indices<Indx...> pi, Ts... ts)
{
    return std::array<T, sizeof...(Indx)>{get<Indx>(ts...)...}; //TADA
}


template <class T, size_t begin, size_t end, class... Ts>
std::array<T, end - begin>
split_array_range(Ts... ts)
{
    typename make_pack_indices<end, begin>::type indices;
    return split_array_range_imp<T>(indices, ts...);
}

template <size_t N>
struct DoubleArray
{
  std::array <int, N> p, q;

  template <typename ... Ts>
  DoubleArray (Ts ... ts) :
  p( split_array_range<int, 0                , sizeof...(Ts) / 2 >(ts...) ),
  q( split_array_range<int, sizeof...(Ts) / 2, sizeof...(Ts)     >(ts...) )
  {
  }
};

int main()
{
    DoubleArray<3> mya{1, 2, 3, 4, 5, 6};
    std::cout << mya.p[0] << "\n" << mya.p[1] << "\n" << mya.p[2] << std::endl;
    std::cout << mya.q[0] << "\n" << mya.q[1] << "\n" << mya.q[2] << std::endl;
}

很短,只是我们需要编写一些帮助程序:

首先我们需要结构体 make_pack_indices,它用于在编译时生成整数范围。比如make_pack_indices&lt;5, 0&gt;::type其实就是pack_indices&lt;0, 1, 2, 3, 4&gt;的类型

template <size_t...>
struct pack_indices {};

template <size_t Sp, class IntPack, size_t Ep>
struct make_indices_imp;

template <size_t Sp, size_t ... Indices, size_t Ep>
struct make_indices_imp<Sp, pack_indices<Indices...>, Ep>
{
    typedef typename make_indices_imp<Sp+1, pack_indices<Indices..., Sp>, Ep>::type type;
};

template <size_t Ep, size_t ... Indices>
struct make_indices_imp<Ep, pack_indices<Indices...>, Ep>
{
    typedef pack_indices<Indices...> type;
};

template <size_t Ep, size_t Sp = 0>
struct make_pack_indices
{
    static_assert(Sp <= Ep, "__make_tuple_indices input error");
    typedef typename make_indices_imp<Sp, pack_indices<>, Ep>::type type;
};

我们还需要一个get()函数,非常类似于std::get for tuple,比如std::get&lt;N&gt;(ts...)返回参数包的第N个元素。

template <class R, size_t Ip, size_t Ij, class... Tp>
struct Get_impl
{
    static R& dispatch(Tp...);
};

template<class R,  size_t Ip, size_t Jp, class Head, class... Tp>
struct Get_impl<R, Ip, Jp, Head, Tp...>
{
    static R& dispatch(Head& h, Tp&... tps)
    {
        return Get_impl<R, Ip, Jp + 1, Tp...>::dispatch(tps...);
    }
};

template<size_t Ip, class Head, class... Tp>
struct Get_impl<Head, Ip, Ip, Head, Tp...>
{
    static Head& dispatch(Head& h, Tp&... tps)
    {
        return h;
    }
};


template <size_t Ip, class ... Tp>
typename pack_element<Ip, Tp...>::type&
get(Tp&... tps)
{
    return Get_impl<typename pack_element<Ip, Tp...>::type, Ip, 0, Tp...>::dispatch(tps...);
}

但要构建 get(),我们还需要一个 pack_element 辅助结构,同样与 std::tuple_element 非常相似,例如 pack_element&lt;N, Ts...&gt;::type 是参数包的第 N 种类型。

template <size_t _Ip, class _Tp>
class pack_element_imp;

template <class ..._Tp>
struct pack_types {};

template <size_t Ip>
class pack_element_imp<Ip, pack_types<> >
{
public:
    static_assert(Ip == 0, "tuple_element index out of range");
    static_assert(Ip != 0, "tuple_element index out of range");
};

template <class Hp, class ...Tp>
class pack_element_imp<0, pack_types<Hp, Tp...> >
{
public:
    typedef Hp type;
};

template <size_t Ip, class Hp, class ...Tp>
class pack_element_imp<Ip, pack_types<Hp, Tp...> >
{
public:
    typedef typename pack_element_imp<Ip-1, pack_types<Tp...> >::type type;
};

template <size_t Ip, class ...Tp>
class pack_element
{
public:
    typedef typename pack_element_imp<Ip, pack_types<Tp...> >::type type;
};

我们开始吧。
其实我真的不明白为什么 pack_element 和 get() 已经不在标准库中了。这些帮助器存在于 std::tuple 中,为什么不用于参数包?

注意:我对 pack_element 和 make_pack_indices 的实现是对 libc++ 中的 std::tuple_element 和 __make_tuple_indices 实现的直接转换。

【讨论】:

  • 我认为这是最干净的解决方案。越看越喜欢。
  • IMO 这应该是公认的答案,因为这是正确的方法。在运行时复制元素具有相同的效果,但会带来完全可以避免的性能损失。
  • 使用 const 引用而不是引用不是更好吗?在什么情况下会首选使用参考?我用 const 引用编译了整个东西,它工作得很好。
【解决方案3】:

这是另一个解决方案:

#include <array>
#include <tuple>
#include <iostream>

template <int i, int o> struct cpyarr_ {
  template < typename T, typename L > static void f (T const& t, L &l) {
    l[i-1] = std::get<i-1+o> (t);
    cpyarr_<i-1,o>::f (t,l);
  }
};

template <int o> struct cpyarr_ <0,o> {
  template < typename T, typename L > static void f (T const&, L&) {}
};

template <int i, int o, typename U, typename ... T> std::array < U, i > cpyarr (U u, T... t) {
  std::tuple < U, T... > l { u, t... };
  std::array < U, i > a; 
  cpyarr_<i,o>::f (l, a); // because std::copy uses call to memmov which is not optimized away (at least with g++ 4.6)
  return a;
}

template <int d> struct a {
  std::array <int, d> p, q;
  template <typename ... T> a (T ... t) : p (cpyarr<d,0> (t...)), q (cpyarr<d,d> (t...)) {} 
};

int main () {
  a <5> x { 0,1,2,3,4,5,6,7,8,9 };
  for (int i = 0; i < 5; i++)
    std::cout << x.p[i] << " " << x.q[i] << "\n";
}

【讨论】:

    【解决方案4】:

    我知道这个问题已经很老了,但是我昨天才在寻找一个非常相似的问题的解决方案时才发现它。我自己制定了一个解决方案,最后编写了一个小型库,我相信它可以满足您的需求。有兴趣的可以找描述here

    【讨论】:

      【解决方案5】:

      请注意,在这种特殊情况下,您可以使用std::initializer_list

      template<int... Is> struct index_sequence{};
      
      template<int N, int... Is> struct make_index_sequence
      {
          typedef typename make_index_sequence<N - 1, N - 1, Is...>::type type;
      };
      
      template<int... Is> struct make_index_sequence<0, Is...>
      {
          typedef index_sequence<Is...> type;
      };
      
      template <int d> struct a {
          std::array <int, d> p, q;
      
          constexpr a (const std::initializer_list<int>& t) :
              a(t, typename make_index_sequence<d>::type())
          {}
      
      private:
          template <int... Is>
          constexpr a(const std::initializer_list<int>& t, index_sequence<Is...>) :
              p ({{(*(t.begin() + Is))...}}),
              q ({{(*(t.begin() + d + Is))...}})
          {}
      };
      

      【讨论】:

        猜你喜欢
        • 2016-12-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-05
        • 1970-01-01
        • 1970-01-01
        • 2013-09-22
        • 2020-03-19
        • 2023-03-07
        相关资源
        最近更新 更多