我如何*有效*从嵌套表达式生成所有类型的元组?

时间:2014-10-19 21:33:15

标签: c++ templates

假设我有一些包含类型排列的模板表达式,在这种情况下它们来自Abstract Syntax Tree

template <typename... Children>                                                    
struct Branch                                                                      
{                                                                                  
};                                                                                 

template <int param>                                                               
struct Leaf                                                                        
{                                                                                  
};

输入表达式可以是BranchLeaf类型的任何嵌套组合,但为了保持简单,我将创建一个包含单个Leaf包裹{{1}的线性AST N类型深层的图层:

Branch

为了这个问题,我已经创建了一个动态生成这些表达式的函数,所以我可以用剧情证明我遇到的问题。所以这是我将用于生成表达式的函数:

using Expression =
  Branch<
    Branch<
      Leaf>>; // N = 2

Live example for N = 25

请注意,该解决方案应适用于任何分支和树叶组合,包括每个分支的多个分支/叶组合,而不仅仅适用于// wrap Leaf in Branch N number of times: template <int N, typename T = Leaf> struct Nest { using type = typename Nest<N-1, Branch<T>>::type; }; template <typename T> struct Nest<0, T> { using type = T; }; 创建的有限集合。我只是使用Nest,这样我就可以生成下面的图,而无需手动写出巨大的表达式。 现在,我的问题是,我如何从这个表达式中有效地提取所有实例化的Nest类型?

因此,对于Branch,如上所示,我希望将以下内容作为输出:

N == 2

它不一定是一个元组,它可以是任何东西,但它 必须能够接受任何数量的类型而没有严重的hackery,所以std::tuple< Branch<Branch<Leaf>>, Branch<Leaf>>; 类型是不可能,至少在boost::mpl。为了这个问题我会使用一个元组。

这是我到目前为止所做的:

Boost 1.56

完整的程序,它实例化表达式,然后是分支类型can be seen here

我对namespace detail { // a container of types template <typename... T> struct Types {}; template <typename T, typename Enabled = void> struct UnfoldImpl; template <template <typename...> class Branch, typename... Children> struct UnfoldImpl< Types<Branch<Children...>>, typename std::enable_if<Branch<Children...>::IsBranch::value>::type> { using type = typename TupleCat< std::tuple<Types<Branch<Children...>>>, typename UnfoldImpl<Types<Children...>>::type>::type; }; template <typename Leaf> struct UnfoldImpl< Types<Leaf>, typename std::enable_if<!Leaf::IsBranch::value>::type> { using type = std::tuple<>; }; template <typename FirstBranch, typename... OtherBranches> struct UnfoldImpl<Types<FirstBranch, OtherBranches...>,typename std::enable_if<sizeof...(OtherBranches)>::type> { using type = typename TupleCat< typename UnfoldImpl<Types<FirstBranch>>::type, typename UnfoldImpl<Types<OtherBranches...>>::type>::type; }; } // Take an expression containing some combination of branch and leaf classes, and extract every // type that is a template instantiation of Branch and place it into a tuple. template <typename Expression> struct Unfold : detail::UnfoldImpl<detail::Types<Expression>> {}; 的实施有效,但似乎效率极低。下面是使用GCC 4.9.1进行编译时的总驻留内存,仅使用Unfold标志,使用命令std=c++11

Peak resident memory vs expression depth

红线表示编译期间的峰值驻留内存(由time -v g++ -std=c++11 main.cpp测量)仅生成表达式(即在time -v gcc ...中实例化类型Nest<N>::type)和蓝色line表示向其添加main()类型的实例化,其中Unfold<Expression>::typeExpression的输出。

我很高兴红线看起来不变,这表明编译器可能在这里做得不错。然而,蓝线显然是多项式的,我想知道是否有任何简单的方法将其降低,理想情况下是线性的,尽管Nest<N>也会很好。

我的问题是:如何将Nlog(N)的效率提高到优于O(N ^ 2)的效果?

我已经问过这个问题的一般形式(How can I reduce the compile-time memory footprint of large templates?),但我在将这些解决方案应用于这个特定情况时遇到了麻烦,并希望得到一些指导。

1 个答案:

答案 0 :(得分:1)

黄金法则是简化。并且不要使用tuple

template <typename...> struct type_list {using type = type_list;};

template<typename...>
struct cat_type_list;

template<typename T>
struct cat_type_list<T> : T {};

template<typename... T, typename... U, typename... R>
struct cat_type_list<type_list<T...>, type_list<U...>, R...> :
    cat_type_list<type_list<T..., U...>, R...> {};


template <typename... AllBranches>
struct Unfold
{
    using type = typename cat_type_list<
        typename Unfold<AllBranches>::type...>::type;
};

template <typename T>
struct Unfold<T>
{
    using type = type_list<>;
};

template <template <typename...> class Branch, typename... Children>
struct Unfold<Branch<Children...>>
{
    using type = typename cat_type_list<
        type_list<Branch<Children...>>,
        typename Unfold<Children...>::type>::type;
};

Demo。我将N改为〜500而不是50时,编译双倍所需的时间从大约150到320毫秒。

这是一个精彩的图表,显示了编译程序时GCC的峰值内存使用情况 - 值由for lim in {5..800..5}; do /usr/local/bin/time -f"%M" g++ -DLIMIT=$lim -std=c++11 ~/Programming/Saves/TEMPS/TEMP2.cxx; done收集:

enter image description here

空间复杂度对我来说似乎是线性的。