【问题标题】:How can I use Variadic templates to flatten a tree of types?如何使用可变参数模板来展平类型树?
【发布时间】:2013-11-19 06:38:49
【问题描述】:

我有一个这样的结构:

template<typename... Ts>
struct List {}

typedef List<char,List<int,float,List<int,unsigned char>>,List<unsigned,short>> MyList;

我想基本上将它扁平化为一个列表。什么是最好的方法?我想如果我摆弄它足够长的时间,我可以用递归来做一些事情,但有些事情告诉我应该有更好的方法。

上述树的结果我想要的应该与此类似:

typedef List<char,int,float,int,unsigned char,unsigned,short> FlattenedList;

这是我的第一次尝试:

template<typename... Ts>
struct List{};

template<typename... Ts>
struct FlattenTree{
    typedef List<Ts...> Type;
};
template<typename... Ts, typename... Us, typename... Vs>
struct FlattenTree<Ts...,List<Us...>,Vs...>{
    typedef typename FlattenTree<Ts..., Us..., Vs...>::Type Type;
};

但是会导致这个错误:error C3515: if an argument for a class template partial specialization is a pack expansion it shall be the last argument

rici 指出了here MSVC2013 的抱怨,所以这里没有编译器错误:

§ 14.8.2.5(从类型推导模板参数)第 5 段列出了不能推导模板参数的上下文。相关的是列表中的最后一个:

— A function parameter pack that does not occur at the end of the parameter-declaration-clause.

更新:

我想可以在最后放置一个虚拟参数,继续将第一个参数移到末尾,或者如果它是 List 则将其扩展到前面,并专注于第一个参数是我的虚拟参数以停止递归。不过,对于编译器来说,为了展平列表似乎需要做很多工作。

namespace Detail{
    struct MyMagicType {};
    template<typename T, typename... Ts>
    struct FlattenTree{
        typedef typename FlattenTree<Ts..., T>::Type Type;
    };
    template<typename... Ts>
    struct FlattenTree<MyMagicType,Ts...>{      //termination case
        typedef List<Ts...> Type;
    };
    template<typename... Ts, typename... Us>
    struct FlattenTree<List<Ts...>, Us...>{
        typedef typename FlattenTree<Ts..., Us...>::Type Type;
    };              //expand Ts to front because they may hold more nested Lists
}

template<typename... Ts>
struct FlattenTree{
    typedef typename Detail::FlattenTree<Ts...,Detail::MyMagicType>::Type Type;
};

这适用于 MSVC2013,但我认为这不是最好的方法,因为我需要一个虚拟类型并且它会给编译器带来很多负担。我想将它与包含 500 多个元素的列表一起使用。

【问题讨论】:

  • 我记得听说过许多编译器(sans clang)在模板类中存在多个模板参数的问题。很多 C++03 的“可变参数模板”只有 6-12 个参数是有原因的。

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


【解决方案1】:

这是我使用通用“元树遍历”的解决方案:

#include <iostream>
#include <type_traits>
#include <typeinfo>

template <typename T>
struct HasChildren : std::false_type {};

template <template <typename...> class P, typename... Types>
struct HasChildren<P<Types...>> : std::true_type {};

template <typename, typename> struct Visit;
template <typename, typename, bool> struct VisitHelper;
template <typename, typename> struct LeafAction;

// Here the role of P2<Visited...> is simply to allow LeafAction to carry out its function.  It is not native to the tree structure itself.
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct Visit<P1<First, Rest...>, P2<Visited...>> :
    VisitHelper<P1<First, Rest...>, P2<Visited...>, HasChildren<First>::value> {};

template <template <typename...> class P1, template <typename...> class P2, typename... Visited>
struct Visit<P1<>, P2<Visited...>> {  // End of recursion.  Every leaf in the tree visited.
    using result = P2<Visited...>;
};

// Here First has children, so visit its children, after which visit Rest...
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct VisitHelper<P1<First, Rest...>, P2<Visited...>, true> : Visit<P1<Rest...>, typename Visit<First, P2<Visited...>>::result> {};  // Visit the "subtree" First, and then after that visit Rest...  Need to use ::result so that when visiting Rest..., the latest value of the P2<Visited...> pack is used.

// Here First has no children, so the "leaf action" is carried out. 
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct VisitHelper<P1<First, Rest...>, P2<Visited...>, false> : LeafAction<P1<First, Rest...>, P2<Visited...>> {};

// As a simple example, the leaf action shall simply be appending its type to P<Visited...>.
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct LeafAction<P1<First, Rest...>, P2<Visited...>> : Visit<P1<Rest...>, P2<Visited..., First>> {};  // Having visited First, now visit Rest...

template <typename> struct VisitTree;

template <template <typename...> class P, typename... Types>
struct VisitTree<P<Types...>> : Visit<P<Types...>, P<>> {};

// ---------------------------- 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 <typename...> struct Group;
template <typename...> struct Wrap;
struct Object {};

int main() {
    VisitTree<
        Pack<Pack<int, Object, double>, bool, Pack<char, Pack<int, double, Pack<char, Pack<char, long, short>, int, Object>, char>, double>, long>
    >::result::print();  // int Object double bool char int double char char long short int Object char double long

    std::cout << std::boolalpha << std::is_same<
        VisitTree<Pack<Wrap<int, Object, double>, bool, Group<char, Pack<int, double, Pack<char, Wrap<char, long, short>, int, Object>, char>, double>, long>>::result,
        Pack<int, Object, double, bool, char, int, double, char, char, long, short, int, Object, char, double, long>
    >::value << std::endl;  // true
}

【讨论】:

    【解决方案2】:

    您的解决方案是非常优雅的 IMO,这是我脑海中的另一个:

    // the tuple-like class we want to flatten
    // (i.e. the node of the tree, with several children as template parameters)
    template<class... TT>
    struct List
    {};
    
    
    // a join metafunction. Joins multiple Lists into a single List
    // e.g.    List<TT1...>, List<TT2...>, etc., List<TTN...>
    //      => List<TT1..., TT2..., etc., TTN...>
    // requires: all template arguments are `List<..>`s
    template<class... TT>
    struct join
    {
        using type = List<>; // end recursion for no parameters
    };
    
    template<class... TT>
    struct join<List<TT...>>
    {
        using type = List<TT...>; // end recursion for a single parameter
    };
    
    template<class... TT0, class... TT1, class... TT2>
    struct join<List<TT0...>, List<TT1...>, TT2...>
    {
        // join two adjacent lists into one, recurse
        // by joining the two lists `List<TT0...>` and `List<TT1...>`,
        // we get one template argument less for the next instantiation of `join`
        // this recurs until there's only one argument left, which then
        // matches the specialization `struct join<List<TT...>>`
        using type = typename join< List<TT0..., TT1...>, TT2... > :: type;
    };
    
    
    // the flatten metafunction
    // guarantees (all specializations): the nested `type` is a flat `List<..>`
    template<class T>
    struct flatten
    {
        // because of the partial specialization below,
        // this primary template is only used if `T` is not a `List<..>`
        using type = List<T>; // wrap the argument in a `List`
    };
    
    template<class... TT>
    struct flatten<List<TT...>> // if the argument is a `List` of multiple elements
    {
        // then flatten each element of the `List` argument
        // and join the resulting `List<..>`s
        using type = typename join<typename flatten<TT>::type...>::type;
    
        // ex. the argument is `List<List<int>, List<double>>`
        // then `TT...` is deduced to `List<int>, List<double>`
        // `List<int>` flattened is `List<int>`, similarly for `List<double>`
        // `join< List<int>, List<double> >` yields `List<int, double>`
    };
    

    使用及测试代码:

    #include <iostream>
    template<class T>
    void print(T)
    {
        std::cout << __PRETTY_FUNCTION__ << "\n"; // NON-STANDARD
    }
    
    int main()
    {
        typedef List<char,List<int,float,List<int,unsigned char>>,
                     List<unsigned,short>> MyList;
        print( flatten<MyList>::type{} );
    }
    

    我确信最简单的方法是使用 boost::MPL ;)

    【讨论】:

    • boost MPL 是否正确使用了可变参数?我什至无法使用 MSVC2013 正确编译 boost 1.54,所以我无法想象它们是最新的。
    • @DyP 您能否将 cmets 添加到代码中。我很难理解这一点(我试图理解这个多参数包)。谢谢。
    • 我不太理解这一行join&lt; List&lt;TT0..., TT1...&gt;, TT2... &gt; :: type; 。它将实例化哪个模板? struct join&lt;List&lt;TT...&gt;&gt; 对吗?
    • @Koushik 这取决于TT2...。本质上,struct join&lt;List&lt;TT0...&gt;, List&lt;TT1...&gt;, TT2...&gt; 递归组合了第一个和第二个 List,直到只剩下一个 List,然后匹配 struct join&lt;List&lt;TT...&gt;&gt;
    • 哦,非常感谢。这是一个非常漂亮的设计。你觉得这个设计怎么样?你能推荐任何我可以用来加强我的模板技能的资源吗?特别是可变参数?比你又一次:-)
    【解决方案3】:

    另一种方法是使用辅助类和累加器列表来代替MyMagicType。我们从一个空的List&lt;&gt; 开始,然后用输入列表中的类型填充它:

    #include <type_traits>
    
    template <class... Ts> struct List {};
    
    // first parameter - accumulator
    // second parameter - input list
    template <class T, class U>
    struct flatten_helper;
    
    // first case - the head of the List is List too
    // expand this List and continue
    template <class... Ts, class... Heads, class... Tail>
    struct flatten_helper<List<Ts...>, List<List<Heads...>, Tail...>> {
        using type = typename flatten_helper<List<Ts...>, List<Heads..., Tail...>>::type;
    };
    
    // second case - the head of the List is not a List
    // append it to our new, flattened list
    template <class... Ts, class Head, class... Tail>
    struct flatten_helper<List<Ts...>, List<Head, Tail...>> {
        using type = typename flatten_helper<List<Ts..., Head>, List<Tail...>>::type;
    };
    
    // base case - input List is empty
    // return our flattened list
    template <class... Ts>
    struct flatten_helper<List<Ts...>, List<>> {
        using type = List<Ts...>;
    };
    
    // wrapper around flatten_helper
    template <class T> struct flatten;
    
    // start with an empty accumulator
    template <class... Ts>
    struct flatten<List<Ts...>> {
        using type = typename flatten_helper<List<>, List<Ts...>>::type;
    };
    
    auto main() -> int {
        using Types = List<int, List<float, List<double, List<char>>>>;
        using Flat = flatten<Types>::type;
    
        static_assert(std::is_same<Flat, List<int, float, double, char>>::value, "Not the same");
    }
    

    【讨论】:

    • 对于一个已经很扁平的列表(没有那么深的嵌套),这可能远没有那么复杂,因为它的迭代次数比我的少得多
    • @PorkyBrain 嗯,对我来说似乎是一样的。对每种类型和每个List 进行一次递归调用。
    猜你喜欢
    • 1970-01-01
    • 2016-01-22
    • 2012-03-27
    • 2021-10-01
    • 2019-04-08
    • 1970-01-01
    • 1970-01-01
    • 2017-09-03
    • 2021-03-08
    相关资源
    最近更新 更多