编译时类型的排列P(N,R)

时间:2016-04-07 02:52:58

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

当包装具有所有不同类型时,我已经为包计算了P(N,R)的工作代码,例如

    PermutationN<2, P<int, char, bool>>

    P< P<int, char>, P<int, bool>, P<char, int>, P<char, bool>, P<bool, int>, P<bool, char> >

但是当有重复元素时,我得到了错误的结果。例如,

PermutationN<2, P<int, int, char>>

应该是

P< P<int, int>, P<int, char>, P<char, int> >

这是我所有类型不同时的工作代码。我坚持如何调整它,以便在包装中有重复类型时给出正确的结果。任何帮助将不胜感激。

#include <iostream>
#include <type_traits>

template <typename, typename> struct Merge;

template <template <typename...> class P, typename... Ts, typename... Us>
struct Merge<P<Ts...>, P<Us...>> {
    using type = P<Ts..., Us...>;
};

template <std::size_t N, typename Pack, typename Previous, typename... Output> struct PermutationNHelper;

template <std::size_t N, template <typename...> class P, typename First, typename... Rest, typename... Prev, typename... Output>
struct PermutationNHelper<N, P<First, Rest...>, P<Prev...>, Output...> : Merge<
    // P<Prev..., Rest...> are the remaining elements, thus ensuring that the next
    // element chosen will not be First. The new Prev... is empty since we now start
    // at the first element of P<Prev..., Rest...>.
    typename PermutationNHelper<N-1, P<Prev..., Rest...>, P<>, Output..., First>::type,
    // Using P<Rest...> ensures that the next set of permutations will begin with the
    // type after First, and thus the new Prev... is Prev..., First.
    typename PermutationNHelper<N, P<Rest...>, P<Prev..., First>, Output...>::type
> {};

template <std::size_t N, template <typename...> class P, typename Previous, typename... Output>
struct PermutationNHelper<N, P<>, Previous, Output...> {
    using type = P<>;
};

template <template <typename...> class P, typename First, typename... Rest, typename... Prev, typename... Output>
struct PermutationNHelper<0, P<First, Rest...>, P<Prev...>, Output...> {
    using type = P<P<Output...>>;
};

template <template <typename...> class P, typename Previous, typename... Output>
struct PermutationNHelper<0, P<>, Previous, Output...> {
    using type = P<P<Output...>>;
};

template <typename Pack> struct EmptyPack;

template <template <typename...> class P, typename... Ts>
struct EmptyPack<P<Ts...>> { using type = P<>; };

template <std::size_t N, typename Pack>
using PermutationN = typename PermutationNHelper<N, Pack, typename EmptyPack<Pack>::type>::type;

// Testing
template <typename...> struct P;

int main() {
    std::cout << std::is_same<
        PermutationN<2, P<int, char, bool>>,
        P< P<int, char>, P<int, bool>, P<char, int>, P<char, bool>, P<bool, int>, P<bool, char> >
    >::value << '\n';  // true

    std::cout << std::is_same<
        PermutationN<2, P<int, int, int>>,
        P< P<int, int>, P<int, int>, P<int, int>, P<int, int>, P<int, int>, P<int, int> >
    >::value << '\n';  // true (but the answer should be P< P<int, int> >.
}

N.B。我正在寻找一种优雅(高效)的解决方案,它不仅仅是执行上述操作,而只是从输出中删除所有重复包(我已经可以这样做但拒绝写出这样一个丑陋,低效的解决方案,不解决问题的核心),而是直接得到正确的输出。这就是我被困的地方。

1 个答案:

答案 0 :(得分:1)

基本思想是将初始类型列表处理为(type, count)对列表,并从那里开始工作。首先,一些原语:

template<class, size_t> struct counted_type {};
template<class...> struct pack {};

我们的代表将是pack counted_type。要构建它,我们需要能够为它添加一个类型:

template<class T, class CT> struct do_push;
template<class T, class...Ts, size_t... Is>
struct do_push<T, pack<counted_type<Ts, Is>...>>{
   using type = std::conditional_t<std::disjunction_v<std::is_same<Ts, T>...>,
        pack<counted_type<Ts, Is + (std::is_same_v<Ts, T>? 1 : 0)>...>,
        pack<counted_type<Ts, Is>..., counted_type<T, 1>>
        >;
};
template<class T, class CT> using push = typename do_push<T, CT>::type;

如果类型已经存在,我们将1添加到计数中;否则我们追加counted_type<T, 1>

稍后使用它,我们需要能够从中删除一个类型:

template<class T, class CT> struct do_pop;
template<class T, class...Ts, std::size_t... Is>
struct do_pop<T, pack<counted_type<Ts, Is>...>>{
   using type = remove<counted_type<T, 0>,
                       pack<counted_type<Ts, Is - (std::is_same_v<Ts, T>? 1 : 0)>...>>;
};

template<class T, class CT> using pop = typename do_pop<T, CT>::type;

remove<T, pack<Ts...>>T移除Ts...的第一个实例(如果存在),并返回生成的包(如果T不存在,则为没有变化)。 (相当无聊的)实现留给读者练习。

使用push,我们可以轻松地从pack类型构建counted_type pack个:

template<class P, class CT = pack<> >
struct count_types_imp { using type = CT; };

template<class CT, class T, class... Ts>
struct count_types_imp<pack<T, Ts...>, CT>
        : count_types_imp<pack<Ts...>, push<T, CT>> {};

template<class P>
using count_types = typename count_types_imp<P>::type;

现在,实际的实现是

template<class T> struct identity { using type = T; };

template <std::size_t N, typename CT, typename = pack<> > struct PermutationNHelper;

// Workaround for GCC's partial ordering failure
template <std::size_t N, class CT, class> struct PermutationNHelper1;

template <std::size_t N, class... Types, std::size_t... Counts, class... Current >
struct PermutationNHelper1<N, pack<counted_type<Types, Counts>...>, pack<Current...>> {
    // The next item can be anything in Types...
    // We append it to Current... and pop it from the list of types, then
    // recursively generate the remaining items
    // Do this for every type in Types..., and concatenate the result.
    using type = concat<
        typename PermutationNHelper<N-1, pop<Types, pack<counted_type<Types, Counts>...>>,
                                    pack<Current..., Types>>::type...
        >;
};


template <std::size_t N, class... Types, std::size_t... Counts, class... Current >
struct PermutationNHelper<N, pack<counted_type<Types, Counts>...>, pack<Current...>> {
    using type = typename std::conditional_t<
                     N == 0,
                     identity<pack<pack<Current...>>>,
                     PermutationNHelper1<N, pack<counted_type<Types, Counts>...>, 
                                            pack<Current...>>
                  >::type; 
     // Note that we don't attempt to evaluate PermutationNHelper1<...>::type 
     // until we are sure that N > 0
};


template <std::size_t N, typename Pack>
using PermutationN = typename PermutationNHelper<N, count_types<Pack>>::type;

通常这可以在一个带有两个部分特化的模板中完成(一个用于N> 0,一个用于N == 0),但是GCC的部分排序是错误的,所以我用{{1明确调度}}。实际上评估conditionalPermutationNHelper1等于0的包扩展将会爆炸性地爆发,因此引入了缺乏想象力的N以提供额外的间接级别并防止爆炸。

PermutationNHelper1只是concat的一个可变版本(嗯,Merge)。实施留给读者练习。