拆分可变参数模板参数
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
拆分为两个单独的tuple
s 或boost::fusion::vector
s。我会编写一个演示代码,但目前我手头只有 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<5, 0>::type
其实就是pack_indices<0, 1, 2, 3, 4>
的类型
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<N>(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<N, Ts...>::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))...)
;
【讨论】:
以上是关于拆分可变参数模板参数的主要内容,如果未能解决你的问题,请参考以下文章