在编译时对类型的置换P(N,R)

Permutation P(N,R) of types in compile time

本文关键字:编译 类型      更新时间:2023-10-16

我已经编写了一个工作代码,用于在包具有所有不同类型时计算包的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> >.
}

注意:我正在寻找一个优雅(高效)的解决方案,它不是简单地执行上述操作,然后只从输出中删除所有重复包(我已经可以做到了,但拒绝写这样一个丑陋、低效的解决方案来解决问题的核心),而是直接获得正确的输出。这就是我被卡住的地方。

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

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

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

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;否则我们附加一个CCD_ 4。

为了以后使用它,我们需要能够从中删除一种类型:

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...>>Ts...中删除T的第一个实例(如果它存在),并返回生成的包(如果T不存在,则返回的包不变)。(相当无聊的)实现留给读者练习。

使用push,我们可以很容易地从以下类型的pack构建counted_types的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的部分排序有缺陷,所以我使用conditional显式调度。事实上,在N等于0的情况下评估PermutationNHelper1中的包扩展会发生可怕的爆炸,因此引入了缺乏想象力的PermutationNHelper1,以提供额外的间接性水平并防止爆炸。

concat只是您的Merge(好吧,typename Merge<...>::type)的可变版本。实现留给读者练习。