【问题标题】:Permutation P(N,R) of types in compile time编译时类型的排列 P(N,R)
【发布时间】:2016-07-27 17:27:22
【问题描述】:

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

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

【问题讨论】:

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


    【解决方案1】:

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

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

    我们的代表将是counted_types 中的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;否则我们附加一个counted_type&lt;T, 1&gt;

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

    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&lt;T, pack&lt;Ts...&gt;&gt;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 显式调度。实际上,在 PermutationNHelper1 中评估包扩展时,N 等于 0 会发生可怕的爆炸,因此引入了难以想象的命名 PermutationNHelper1 以提供额外的间接级别并防止爆炸。

    concat 只是您的Merge 的可变参数版本(好吧,typename Merge&lt;...&gt;::type)。该实现留给读者作为练习。

    【讨论】:

    • @T.C.感谢我见过的最漂亮的解决方案。我已经验证它可以正常工作。只要我们使用std::enable_if 来分隔 N > 0 和 N == 0 的情况,GCC 5.3 确实允许它只使用一个具有两个部分特化的模板进行编译。这是整个工作代码:ideone.com/hqL11N
    • @prestokeys 是的,但我觉得enable_if 更丑。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-21
    • 2021-10-14
    相关资源
    最近更新 更多