拆分可变参数模板参数

Posted

技术标签:

【中文标题】拆分可变参数模板参数【英文标题】: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。遗憾的是,构造函数的多个参数包是非法的,尽管在这种情况下应该清楚每个包中的类型。 【参考方案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> mya1, 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))...)
    
;

【讨论】:

以上是关于拆分可变参数模板参数的主要内容,如果未能解决你的问题,请参考以下文章

可变参数模板类:是不是可以为每个可变参数模板参数实现一个唯一的成员函数?

[C++11 模板的改进] --- 可变参数模板

C++11 ——— 可变参数模板

C++11 ——— 可变参数模板

C/C++中的可变参数和可变参数模板

为啥可变参数模板的模板特化与非可变模板的特化不同?