从包中获取所有子包

时间:2015-02-11 18:07:09

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

PowerSet<Pack<Types...>>::type是给出一个由Types...的所有子集组成的包组成的包(现在假设静态断言Types...中的每个类型都是不同的)。例如,

PowerSet<Pack<int, char, double>>::type

Pack<Pack<>, Pack<int>, Pack<char>, Pack<double>, Pack<int, char>, Pack<int, double>, Pack<char, double>, Pack<int, char, double>>

现在,我已经解决了这个练习并进行了测试,但我的解决方案很长,并希望听到一些更优雅的想法。我不是要求任何人审查我的解决方案,而是建议一个新的方法,或许用一些伪代码描绘他们的想法。

如果你想知道,这就是我所做的:首先,我从高中回忆起一组N个元素有2 ^ N个子集。每个子集对应于 一个N位二进制数,例如001010 ... 01(N位长),其中0表示元素在子集中,1表示该元素 元素不在子集中。因此000 ... 0表示空子集,111 ... 1表示整个集合本身。 所以使用(模板)序列0,1,2,3,... 2 ^ N-1,我形成2 ^ N个index_sequence,每个对应于二进制表示 该序列中的整数,例如index_sequence&LT; 1,1,0,1&GT;将对应于该序列中的13。然后每个2 ^ N index_sequence的 将被转换为Pack<Types...>所需的2 ^ N个子集。

我的解决方案很长,我知道有一种比上述机械方法更优雅的方法。 如果你想到一个更好的计划(也许更短,因为它更具递归性或其他),请发表您的想法,以便我可以接受 你的更好的计划,希望写出一个更短的解决方案。如果您认为可能需要,我不希望您完整地写出您的解决方案 一段时间(除非你想)。但是目前,我想不出比我做的更好的方式了。这是我目前的长期解决方案,如果你想阅读它:

#include <iostream>
#include <cmath>
#include <typeinfo>

// SubsetFromBinaryDigits<P<Types...>, Is...>::type gives the sub-pack of P<Types...> where 1 takes the type and 0 does not take the type.  The size of the two packs must be the same.
// For example, SubsetFromBinaryDigits<Pack<int, double, char>, 1,0,1>::type gives Pack<int, char>.

template <typename, typename, int...> struct SubsetFromBinaryDigitsHelper;

template <template <typename...> class P, typename... Accumulated, int... Is>
struct SubsetFromBinaryDigitsHelper<P<>, P<Accumulated...>, Is...> {
    using type = P<Accumulated...>;
};

template <template <typename...> class P, typename First, typename... Rest, typename... Accumulated, int FirstInt, int... RestInt>
struct SubsetFromBinaryDigitsHelper<P<First, Rest...>, P<Accumulated...>, FirstInt, RestInt...> :
    std::conditional<FirstInt == 0,
        SubsetFromBinaryDigitsHelper<P<Rest...>, P<Accumulated...>, RestInt...>,
        SubsetFromBinaryDigitsHelper<P<Rest...>, P<Accumulated..., First>, RestInt...>
    >::type {};

template <typename, int...> struct SubsetFromBinaryDigits;

template <template <typename...> class P, typename... Types, int... Is>
struct SubsetFromBinaryDigits<P<Types...>, Is...> : SubsetFromBinaryDigitsHelper<P<Types...>, P<>, Is...> {};

// struct NSubsets<P<Types...>, IntPacks...>::type is a pack of packs, with each inner pack being the subset formed by the IntPacks.
// For example, NSubsets< Pack<int, char, long, Object, float, double, Blob, short>, index_sequence<0,1,1,0,1,0,1,1>, index_sequence<0,1,1,0,1,0,1,0>, index_sequence<1,1,1,0,1,0,1,0> >::type will give
// Pack< Pack<char, long, float, Blob, short>, Pack<char, long, float, Blob>, Pack<int, char, long, float, Blob> >

template <typename, typename, typename...> struct NSubsetsHelper;

template <template <typename...> class P, typename... Types, typename... Accumulated>
struct NSubsetsHelper<P<Types...>, P<Accumulated...>> {
    using type = P<Accumulated...>;
};

template <template <typename...> class P, typename... Types, typename... Accumulated, template <int...> class Z, int... Is, typename... Rest>
struct NSubsetsHelper<P<Types...>, P<Accumulated...>, Z<Is...>, Rest...> :
    NSubsetsHelper<P<Types...>, P<Accumulated..., typename SubsetFromBinaryDigits<P<Types...>, Is...>::type>, Rest...> {};

template <typename, typename...> struct NSubsets;

template <template <typename...> class P, typename... Types, typename... IntPacks>
struct NSubsets<P<Types...>, IntPacks...> : NSubsetsHelper<P<Types...>, P<>, IntPacks...> {};

// Now, given a pack with N types, we transform index_sequence<0,1,2,...,2^N> to a pack of 2^N index_sequence packs, with the 0's and 1's of each
// index_sequence pack forming the binary representation of the integer.  For example, if N = 2, then we have
// Pack<index_sequence<0,0>, index_sequence<0,1>, index_sequence<1,0>, index_sequence<1,1>>.  From these, we can get the
// power set, i.e. the set of all subsets of the original pack.

template <int N, int Exponent, int PowerOfTwo>
struct LargestPowerOfTwoUpToHelper {
    using type = typename std::conditional<(PowerOfTwo > N),
        std::integral_constant<int, Exponent>,
        LargestPowerOfTwoUpToHelper<N, Exponent + 1, 2 * PowerOfTwo>
    >::type;
    static const int value = type::value;
};

template <int N>
struct LargestPowerOfTwoUpTo : std::integral_constant<int, LargestPowerOfTwoUpToHelper<N, -1, 1>::value> {};

constexpr int power (int base, int exponent) {
    return std::pow (base, exponent);
}

template <int...> struct index_sequence {};

// For example, PreBinaryIndexSequence<13>::type is to be index_sequence<0,2,3>, since 13 = 2^3 + 2^2 + 2^0.
template <int N, int... Accumulated>
struct PreBinaryIndexSequence {  // Could use another helper, since LargestPowerOfTwoUpToHelper<N, -1, 1>::value is being used twice.
    using type = typename PreBinaryIndexSequence<N - power(2, LargestPowerOfTwoUpToHelper<N, -1, 1>::value), LargestPowerOfTwoUpToHelper<N, -1, 1>::value, Accumulated...>::type;
};

template <int... Accumulated>
struct PreBinaryIndexSequence<0, Accumulated...> {
    using type = index_sequence<Accumulated...>;
};

// For example, BinaryIndexSequenceHelper<index_sequence<>, index_sequence<0,2,3>, 0, 7>::type is to be index_sequence<1,0,1,1,0,0,0,0> (the first index with position 0, and the last index is position 7).
template <typename, typename, int, int> struct BinaryIndexSequenceHelper;

template <template <int...> class Z, int... Accumulated, int First, int... Rest, int Count, int MaxCount>
struct BinaryIndexSequenceHelper<Z<Accumulated...>, Z<First, Rest...>, Count, MaxCount> : std::conditional<First == Count,
        BinaryIndexSequenceHelper<Z<Accumulated..., 1>, Z<Rest...>, Count + 1, MaxCount>,
        BinaryIndexSequenceHelper<Z<Accumulated..., 0>, Z<First, Rest...>, Count + 1, MaxCount>
    >::type {};

// When the input pack is emptied, but Count is still less than MaxCount, fill the rest of the acccumator pack with 0's.
template <template <int...> class Z, int... Accumulated, int Count, int MaxCount>
struct BinaryIndexSequenceHelper<Z<Accumulated...>, Z<>, Count, MaxCount> : BinaryIndexSequenceHelper<Z<Accumulated..., 0>, Z<>, Count + 1, MaxCount> {};

template <template <int...> class Z, int... Accumulated, int MaxCount>
struct BinaryIndexSequenceHelper<Z<Accumulated...>, Z<>, MaxCount, MaxCount> {
    using type = Z<Accumulated...>;
};

// At last, BinaryIndexSequence<N> is the binary representation of N using index_sequence, e.g. BinaryIndexSequence<13,7> is index_sequence<1,0,1,1,0,0,0>.
template <int N, int NumDigits>
using BinaryIndexSequence = typename BinaryIndexSequenceHelper<index_sequence<>, typename PreBinaryIndexSequence<N>::type, 0, NumDigits>::type;

// Now define make_index_sequence<N> to be index_sequence<0,1,2,...,N-1>.
template <int N, int... Is>
struct make_index_sequence_helper : make_index_sequence_helper<N-1, N-1, Is...> {};  // make_index_sequence_helper<N-1, N-1, Is...> is derived from make_index_sequence_helper<N-2, N-2, N-1, Is...>, which is derived from make_index_sequence_helper<N-3, N-3, N-2, N-1, Is...>, which is derived from ... which is derived from make_index_sequence_helper<0, 0, 1, 2, ..., N-2, N-1, Is...>

template <int... Is>
struct make_index_sequence_helper<0, Is...> {
    using type = index_sequence<Is...>;
};

template <int N>
using make_index_sequence = typename make_index_sequence_helper<N>::type;

// Finally, ready to define PowerSet itself.
template <typename, typename> struct PowerSetHelper;

template <template <typename...> class P, typename... Types, template <int...> class Z, int... Is>
struct PowerSetHelper<P<Types...>, Z<Is...>> : NSubsets< P<Types...>, BinaryIndexSequence<Is, sizeof...(Types)>... > {};

template <typename> struct PowerSet;

template <template <typename...> class P, typename... Types>
struct PowerSet<P<Types...>> : PowerSetHelper<P<Types...>, make_index_sequence<power(2, sizeof...(Types))>> {};

// -----------------------------------------------------------------------------------------------------------------------------------------------
// Testing

template <typename...> struct Pack {};

template <typename Last>
struct Pack<Last> {
    static void print() {std::cout << typeid(Last).name() << std::endl;}
};

template <typename First, typename ... Rest>
struct Pack<First, Rest...> {
    static void print() {std::cout << typeid(First).name() << ' ';  Pack<Rest...>::print();}
};

template <int Last>
struct index_sequence<Last> {
    static void print() {std::cout << Last << std::endl;}
};

template <int First, int ... Rest>
struct index_sequence<First, Rest...> {
    static void print() {std::cout << First << ' ';  index_sequence<Rest...>::print();}
};

int main() {
    PowerSet<Pack<int, char, double>>::type powerSet;
    powerSet.print();
}

4 个答案:

答案 0 :(得分:8)

这是我的尝试:

template<typename,typename> struct Append;

template<typename...Ts,typename T>
struct Append<Pack<Ts...>,T>
{
    using type = Pack<Ts...,T>;
};

template<typename,typename T=Pack<Pack<>>>
struct PowerPack
{
    using type = T;
};

template<typename T,typename...Ts,typename...Us>
struct PowerPack<Pack<T,Ts...>,Pack<Us...>>
    : PowerPack<Pack<Ts...>,Pack<Us...,typename Append<Us,T>::type...>>
{
};

Live example

答案 1 :(得分:6)

关键是要建立一个递归关系:

PowerSet of {A, B, C}
== (PowerSet of {B,C}) U (PowerSet of {B,C} w/ A)

其中w/ A部分仅指将A添加到每个子集中。鉴于此,我们需要三个元函数:Plus,以取两个Pack的联合,Prefix,为Pack中的每个元素添加一个类型,最后,PowerSet。三个P,如果你愿意的话。

按复杂程度递增。 Plus只是将包裹捆绑在一起:

template <typename A, typename B> struct Plus;

template <typename... A, typename... B>
struct Plus<Pack<A...>, Pack<B...>> {
    using type = Pack<A..., B...>;
};

前缀只使用Plus向所有内容添加Pack<A>

template <typename A, typename P> struct Prefix;

template <typename A, typename... P>
struct Prefix<A, Pack<P...> >
{
    using type = Pack<typename Plus<Pack<A>, P>::type...>;
};

然后PowerSet是复发的直接翻译:

template <typename P> struct PowerSet;

template <typename T0, typename... T>
struct PowerSet<Pack<T0, T...>>
{
    using rest = typename PowerSet<Pack<T...>>::type;
    using type = typename Plus<rest,
                               typename Prefix<T0, rest>::type
                               >::type;
};

template <>
struct PowerSet<Pack<>>
{
    using type = Pack<Pack<>>;
};

答案 2 :(得分:2)

使用Eric Niebler's Tiny Meta-Programming LibraryDEMO)作弊:

template <typename...Ts>
using Pack = meta::list<Ts...>;

template <typename Sets, typename Element>
using f = meta::concat<
  Sets,
  meta::transform<
    Sets,
    meta::bind_back<meta::quote<meta::push_front>, Element>
>>;

template <typename List>
using PowerSet = meta::foldr<List, Pack<Pack<>>, meta::quote<f>>;

f获取列表和单个类型的列表,并生成一个列表,其中包含每个输入列表和前面给定类型的每个输入列表。然后,计算powerset只是f {{1}}的原始输入列表。

答案 3 :(得分:1)

这里的目标是创建一些通用的工具,然后在几行中解决pow。通常有用的类型工具也不是那么笨重。

所以,首先是一个用于类型列表操作的库。

template<class...>struct types{using type=types;};

Concat两种类型列表:

template<class types1,class types2>struct concat;
template<class...types1,class...types2>struct concat<
  types<types1...>,
  types<types2...>
>:types<types1...,types2...>{};
template<class A,class B>using concat_t=typename concat<A,B>::type;

将类型函数Z应用于列表的每个元素:

template<template<class...>class Z, class types> struct apply;
template<template<class...>class Z, class...Ts>
struct apply<Z,types<Ts...>>:
  types< Z<Ts>... >
{};
template<template<class...>class Z, class types>
using apply_t=typename apply<Z,types>::type;

部分模板应用程序:

template<template<class...>class Z, class T>
struct partial {
  template<class... Ts>
  struct apply:Z<T,Ts...> {};
  template<class... Ts>
  using apply_t=typename apply<Ts...>::type;
};

取lhs,并在rhs上应用Z<lhs, *>

template<template<class, class...>class Z, class lhs, class types>
using expand_t=apply_t<partial<Z,lhs>::template apply_t, types>;

解决问题:

template<class T>struct pow; // fail if not given a package
template<>struct pow<types<>>:types<types<>>{};
template<class T>using pow_t=typename pow<T>::type;
template<class T0,class...Ts>struct pow<types<T0,Ts...>>:
  concat_t<
    expand_t< concat_t, types<T0>, pow_t<types<Ts...>> >,
    pow_t<types<Ts...>>
  >
{};

您可能只注意到一个非空结构体。这是因为types有一个using type=types;正文,而其他人只是偷了它。

pow被递归地定义为尾部的pow,union {{head} union每个尾部元素}。终止案例处理空电源组元素。

live example

concat_t过于强大,因为我们一次只需追加1个元素。

apply_t仅适用于一元函数,因为它更清晰。但这确实意味着必须编写expand_t。您可以编写一个apply_t,其中包含expand_t,但函数式编程中的部分函数应用是一个很好的习惯。

我必须让partial大约3倍于我喜欢,因为如果我不首先在struct打开包装然后执行using,则铿锵似乎会爆炸。

其中一个版本并不依赖于使用特定的包类型。它在几点爆炸。

我希望有using语法返回template。这会使expand_t无效,因为partial_z<concat_t, T0>可能是template,其前缀为T0,而不是丑陋的partial<concat_t, T0>::template apply_t