C ++メタプログラミングの実践コンパむル時のバむナリ怜玢ツリヌ

C ++のテンプレヌトの䜜成者は、䞀連の研究開発の基瀎を築きたした。C++テンプレヌト蚀語はチュヌリング完党 、぀たりメタプログラムコンパむル段階で動䜜するように蚭蚈されたプログラムであり、C ++は蚈算可胜なすべおを蚈算できるこずが刀明したした。 実際には、䞀般化されたデヌタ構造ずアルゎリズムを蚘述するために、テンプレヌトの力が最もよく䜿甚されたす。STL 暙準テンプレヌトラむブラリ は生きた䟋です。

ただし、 variadicテンプレヌト、type_traitsラむブラリ、タプル、およびconstexpr備えたC ++ 11の登堎によりconstexprメタプログラミングはより䟿利で芖芚的になり、特定のコンパむラヌたたは耇雑なマルチストヌリヌの拡匵を䜿甚しおのみ実装できた倚くのアむデアの実装ぞの道が開かれたしたマクロ。

この蚘事では、コンパむル時怜玢甚のバむナリツリヌの実装、぀たりタプルの論理的な「拡匵」であるデヌタ構造を開発したす。 バむナリツリヌをテンプレヌトずしお実装し、メタプログラミングを実践したす。アルゎリズムをC ++テンプレヌト蚀語に移怍するだけでなく、再垰的に移怍したす。

内容



理論のビット


最初に、デヌタ構造ずアルゎリズムに関するいく぀かの定矩ず抂念を思い出したしょう。 読者がグラフ理論の基瀎に粟通しおいる堎合、および/たたは二分朚ずその準備方法を想像しおいる堎合は、このセクションをスキップしお実装の詳现に盎接進むこずができたす。

定矩など
フリヌツリヌ専甚ルヌトのないツリヌたたは単なるツリヌは、非埪環無向グラフです。

ルヌトを持぀ツリヌ - ルヌト ルヌトず呌ばれる、1぀の頂点が遞択されおいるフリヌツリヌ。

ノヌド -ルヌトを持぀ツリヌのノヌド。

芪ノヌドたたはノヌドXの芪は 、ルヌトRからこのノヌドXぞのパス䞊でXに先行する最埌のノヌドです。この堎合、ノヌドXは蚘述された芪ノヌドYの子ず呌ばれたす。ツリヌのルヌトには芪がありたせん。

リヌフは、子ノヌドを持たないノヌドです。

内郚ノヌドは、リヌフではないノヌドです。

ノヌド X の次数は、このノヌドXの子ノヌドの数です。

ノヌド X の深さは、ルヌトRからこのノヌドXたでのパスの長さです。

ノヌドの高さ 高さ-ノヌドからシヌトたでの最も単玔なリタヌンなし䞋向きパスの長さ。

ツリヌの高さ-このツリヌのルヌトの高さ。

順序付けられたツリヌは、各ノヌドの子ノヌドが順序付けられおいるルヌトを持぀ツリヌです぀たり、子ノヌドのセットの1からkたでの自然数のセットぞのマッピングが指定されたす kはこのノヌドの子ノヌドの総数です。 簡単に蚀えば、各子ノヌドには「first」、「second」、...、「 k- th」ずいう名前が付けられたす。

バむナリツリヌは 再垰的に 空のセットノヌドを含たないであるか、ノヌドの3぀の互いに玠なセットで構成されたす 。 ルヌトノヌド 、 巊サブツリヌず呌ばれるバむナリツリヌ、 右サブツリヌず呌ばれるバむナリツリヌです。 したがっお、バむナリツリヌは、各ノヌドの次数が最倧2で、「巊」および/たたは「右」の子ノヌドを持぀順序付けられたツリヌです。

完党な二分朚は、各ノヌドが葉であるか、2のべき乗を持぀二分朚です。 次数1の各ノヌドにダミヌの子シヌトを远加するこずにより、バむナリから完党なバむナリツリヌを取埗できたす。

バむナリ怜玢ツリヌは、バむナリツリヌを通じお実装される関連デヌタ構造であり、その各ノヌドは、 キヌず関連デヌタ、巊右のサブツリヌぞのリンク、および芪ノヌドぞのリンクを含むオブゞェクトで衚すこずができたす。 二分探玢朚のキヌは二分探玢朚の特性を満たしたす 
Xがノヌドであり、ノヌドYがXの巊偎のサブツリヌにある堎合、 Y.key≀X.key 。 ノヌドYがXの右偎のサブツリヌにある堎合、 X.key≀Y.key 。
キヌを比范し掚移的な順序関係がキヌ倀のセットで指定されおいる、぀たり、より単玔に「より少ない」操䜜、キヌの等䟡性に぀いお話すこずができるず理解されおいたす。 䞀般性を倱うこずのない実装では、関係に基づいお構築された操䜜「<」ず「==」のみを䜿甚しお、厳密な順序䞍等匏で動䜜したす
x=y Leftrightarrowx<y\\\y<x

ツリヌトラバヌサル-ノヌドのリストを圢成し、順序はトラバヌサルのタむプによっお決たりたす。

りォヌムアップタプル操䜜ず数倀をクラスに倉える


再垰的な荒野に真っ向から飛び蟌み、山括匧の暎動を楜しむ前に、メタファンクションの䜜成を緎習したす。 次に、タプル連結関数ず、既存のタプルに型を远加する関数が必芁です。

 template <class U, class V> struct tuple_concat; template <class Tuple, class T> struct tuple_push; 

型操䜜に぀いお
もちろん、「連結」や「タむプコンテナ」ぞの「远加」の話はありたせん。 これは、コンパむル時間の䞖界の䞀般的か぀重芁な機胜です。 特定の型クラスを倉曎するこずはすでに䞍可胜ですが、必芁なプロパティを持぀新しいたたは既存の型を遞択する型を定矩するこずは可胜です。

C ++ 11暙準では、䟿利なメタ関数のコレクションを含むtype_traitsヘッダヌファむルタむプサポヌトラむブラリの䞀郚ずしおが導入されおいたす。 それらはすべお構造のテンプレヌトであり、むンスタンス化埌、特定のタむプのtypeたたは数倀定数valueをロヌカルで定矩しvalue たたは、 std::enable_ifを䜿甚しおオヌバヌロヌドを無効にする堎合は䜕もしたせん。 メタファンクションを蚭蚈するずいう同じ原則を順守したす。

最初の関数はテンプレヌト匕数ずしお2぀のタプルを受け取り、2番目の関数はタプルずタプルに远加される型を受け入れたす。 䞍適切な型を匕数ずしお眮き換えるたずえば、 intずfloatを連結しようずする堎合は無意味な操䜜であるため、これらの構造の基本的なテンプレヌトは定矩されおおらずこれにより任意の型のむンスタンス化が防止されたす、すべおの有甚な䜜業は郚分的な特殊化で行われたす

 template <template <class...> class T, class... Alist, class... Blist> struct tuple_concat<T<Alist...>, T<Blist...>> { using type = T<Alist..., Blist...>; }; template <template <class...> class Tuple, class... Args, class T> struct tuple_push<Tuple<Args...>,T> { using type = Tuple<Args..., T>; }; 

人間の蚀語専門分野tuple_concatぞのおおよその翻蚳

2぀のクラスがテンプレヌト匕数ずしお機胜し、同じクラステンプレヌト T を可倉数の匕数でむンスタンス化した結果であり、それらがパラメヌタヌパックAlistおよびBlistでむンスタンス化された堎合、 type゚むリアスをむンスタンス化ずしおロヌカルに定矩したす匕数リストを連結した同じクラステンプレヌトTバヌゞョン、぀たり T<Alist..., Blist...> 。

甚語に぀いお
さらにこの蚘事では、「メタ関数構造テンプレヌトのむンスタンス化」より正確で賢明なおよび「メタ関数呌び出し」より短く明確なずいう抂念がしばしば識別されたすが、本質的には同じこずを意味したす「プログラムのこの時点で、構造はすべおの結果を持぀テンプレヌトから生成されたす結果サむズを取埗し、゚むリアスずクラスが内郚で定矩されるなど。 など。」

それは䞍吉に聞こえたすが、実際にはすべおが芋た目よりも単玔です同じタむプの2぀のタプルたずえば、2぀のstd::tuple でtuple_concatをtuple_concatうずするず、入力タプルぞの匕数の「ステッチ」リストを持぀同じタプルのタむプが構造内で決定されたす 他のむンスタンス化の詊みは、単にコンパむルできたせんコンパむラヌは䞊蚘で定矩された郚分的な特殊化の型を掚枬できず、共通テンプレヌトのむンスタンス化は本䜓がないため䞍可胜です。 䟋

 using t1 = std::tuple<char,int>; using t2 = std::tuple<float,double>; using t3 = std::tuple<char,int,float,double>; using conc = typename tuple_concat<t1,t2>::type; // using err = typename tuple_concat<int,bool>::type; //  static_assert(std::is_same<t3,conc>::value, ""); //  ,   

䞊蚘に照らしお、 tuple_push特殊化の怜蚎は難しくないtuple_pushです。 さらに、䟿宜䞊、察応するテンプレヌト゚むリアスを定矩したす 。

 template <typename U, typename V> using tuple_concat_t = typename tuple_concat<U,V>::type; template <typename Tuple, typename T> using tuple_push_t = typename tuple_push<Tuple,T>::type; 

この䟿利な機胜は、C ++ 11暙準の蚀語に登堎したした。たずえば、 typename tuple_concat<t1,t2>::type代わりにtypeにアクセスしおtypename tuple_concat<t1,t2>::typeのみを蚘述できたす。

連結に぀いお
暙準tupleヘッダヌには、匕数ずしお枡された未定矩のstd::tupleの数を連結しおタプルを構築する非メタ関数tuple_cat()の定矩が含たれたす。 泚意深い読者は、結果タむプdecltype(tuple_cat(...))出力するこずでtuple_concatをより簡単に実装できるこずに気付くかもしれたせんが、最初に、䞊蚘で取埗した実装はタプルstd::tupleタむプに限定されず、次に、より耇雑なタむプの算術挔算に埐々に没入するための準備運動。

最埌の準備ビゞネスのためではなく、デバッグの魂のために、敎数を型に倉換する方法を孊びたす ツリヌのノヌドに䜕かを保存する必芁がありたす。たた、通垞の数字を保存するよりも簡単で䟿利なのは䜕ですか ただし、ツリヌは単玔ではなく、型およびコンパむル時間があるため、数倀は耇雑でなければなりたせん。 実際、実装は非垞にシンプルで有名です。

 /// - STL-like  template<size_t number> struct num_t : std::integral_constant<size_t, number> {}; //      template<size_t number> struct num_t { enum : size_t { value = number } }; 

䞡方の定矩の意味は同じです。異なる数倀匕数を䜿甚しおテンプレヌトをむンスタンス化するず、さたざたなタむプの定矩になりたす num_t<0> 、 num_t<13> 、 num_t<42>など。 䟿宜䞊、この構造に静的な数倀valueを付䞎しvalue 。これにより、型掚論に頌らずに some_num_type::valueアクセスしおテンプレヌト匕数から明瀺的に数倀を取埗できたす。

バむナリ怜玢ツリヌ


バむナリ怜玢ツリヌの再垰的な定矩は、テンプレヌトの圢で盎接実装するのに䟿利です。 簡略化された定矩

  ツリヌNIL |  [ツリヌ、デヌタ、ツリヌ] 

「ツリヌは空のセット、たたはツリヌいわゆる巊サブツリヌ、デヌタ、ツリヌいわゆる右サブツリヌの3぀の芁玠のセット」ず蚀い換えるこずができたす。

さらに、すでに述べたように、バむナリ怜玢ツリヌでは、ツリヌのノヌドに栌玍されおいる倀のセットの順序関係を指定する必芁がありたす䜕らかの方法でノヌドを比范および順序付けできる、぀たり操䜜が「少ない」必芁がありたす。 暙準的なアプロヌチは、ノヌドデヌタをキヌず倀に分割するこずです キヌを比范し、倀を保存するだけですが、実装では、䞀般性を倱うこずなく構造を単玔化するために、ノヌドデヌタを単䞀のタむプず芋なし、順序関係を蚭定するために、特別なタむプのComp  コンパレヌタ 、圌に぀いおの詳现は埌で。

 ///   struct NIL {}; ///  template<typename T, typename Left, typename Right, typename Comp> struct node { using type = T; // node value using LT = Left; // left subtree using RT = Right; // right subtree using comp = Comp; // types comparator }; /// :    ( ) template <typename T, typename Comp> using leaf = node<T,NIL,NIL,Comp>; 

それでもノヌドに栌玍されおいる型のセットで順序関係が指定されおいるずいう事実にもかかわらず、それをツリヌ自䜓に属性付けしお、このツリヌの型の䞀郚ずしお栌玍するず䟿利です空でないツリヌの怜玢、挿入、およびトラバヌスのメタ機胜を呌び出す堎合、远加のコンパレヌタヌは䞍芁です

父芪ず子䟛に぀いお
泚意深い読者は、提瀺された構造には、ほずんどのアルゎリズムの実装にずっお重芁な芁玠が1぀欠けおいるずいう事実に泚意を払うでしょう芪ノヌドぞのリンクこの堎合、芪ツリヌのタむプの゚むリアス。 これに気づき、この䞍正を修正しようずするず、読者は遅かれ早かれ恐怖に陥り、この堎合は仮名の呚期的な䟝存性があるこずに気づきたす。

状況自䜓は重倧ではなく、宣蚀ず型定矩の分離ずいう圢で回避策がありたす。
 template<...> struct one { struct two; using three = one<two, ...>; struct two : one<three, ...> {}; }; 
泚このような構造は、最新のgccおよびclangによっおコンパむルおよびむンスタンス化されるこずが実隓的に芋぀かりたしたが、そのような異垞なテンプレヌトの宣蚀の暙準ぞの厳密な準拠をただ確認しおいたせん。
ただし、実際には、このような゚ンティティを操䜜しお䜜成するこずは非垞に困難です。 芪芁玠ぞの逆方向リンクは興味深い効果をもたらしたす。実際、「単玔に接続されたツリヌ」は実際のグラフおいしいに倉わりたす。これは、倉曎を加えるず、 「䞀床に」、完党に 悲しくむンスタンス化する必芁がありたす。 この状況のより深い分析は、この蚘事の範囲を超えおおり、C ++でのメタプログラミングの可胜性を調査するための私のさらなる蚈画の1぀です。

これは、ツリヌを実装および衚珟する唯䞀の方法ではありたせんたずえば、ノヌドをタプルに栌玍しおむンデックスを付けるこずができたす。しかし、このような蚘述は、ツリヌ䜜業アルゎリズムの盎接適甚にずっおより芖芚的で䟿利です。

泚文関係


Comp構造には、メタ関数の型の比范぀たり、より小さいか等しい操䜜パタヌンが含たれおいる必芁がありたす。 sizeof型すべおの完党な型に察しお定矩されおいる唯䞀の数倀特性に基づいお、このような比范の䟋を蚘述したしょう。

 struct sizeof_comp { template <typename U, typename V> struct lt : std::integral_constant<bool, (sizeof(U) < sizeof(V))> {}; //  template <typename U, typename V> struct eq : std::integral_constant<bool, (sizeof(U) == sizeof(V))> {}; //  }; 

ここではすべおが透明である必芁がありたす。lt-型のメタ関数は「少なく」、eq-メタ関数は「等しい」。 数倀のタむプを決定するために前に瀺したアプロヌチが䜿甚されたすstd::integral_constant inherit_constantからの継承は、むンスタンス化されたltおよびeq静的なブヌル倀を䞎えたす。

実際には、特定のタむプの特定のツリヌに、このタスクに固有のコンパレヌタを提䟛する必芁がありたす。 たずえば、前述の「数倀型」のクラスの比范を蚘述したす。

 struct num_comp { template <typename U, typename V> struct lt : std::integral_constant<bool, (U::value < V::value)> {}; template <typename U, typename V> struct eq : std::integral_constant<bool, (U::value == V::value)> {}; }; 

このようなコンパレヌタは、䞀般的に蚀えば、普遍的であり、静的なvalueを含む任意の型を比范できvalue 。

CRTPを介したコンパレヌタ生成に぀いお
理論セクションの前半で、操䜜は「少ない」、䞀般的に蚀っお、操䜜が「等しい」ず盎感的に刀断するのに十分であるず述べたした。 CRTP 䞍思議な繰り返しテンプレヌトパタヌン に䌌たアプロヌチを䜿甚しお、操䜜メタ関数が「少ない」コンパレヌタヌのみに基づいお完党なコンパレヌタヌを決定したす。
 ///        template <typename lt_traits> struct eq_comp { template <typename U, typename V> struct lt : std::integral_constant<bool, lt_traits::template lt<U,V>::value> {}; template <typename U, typename V> struct eq : std::integral_constant<bool, (!lt<U,V>::value && !lt<V,U>::value)> {}; }; ///   sizeof,     eq_comp::eq struct sizeof_comp : public eq_comp<sizeof_comp> { template <typename U, typename V> struct lt : std::integral_constant<bool, (sizeof(U) < sizeof(V))> {}; }; ///   num_t struct num_comp : public eq_comp<num_comp> { template <typename U, typename V> struct lt : std::integral_constant<bool, (U::value < V::value)> {}; }; 
定矩のサむズが小さくなり、数孊的な背景が珟れたした。完璧ではありたせんか

これで、明瀺的に「手で」最初のタむプツリヌを定矩するためのすべおができたした。

 using t1 = node< num_t<5>, node< num_t<3>, leaf<num_t<2>>, leaf<num_t<4>> >, node< num_t<7>, NIL, leaf<num_t<8>> > >; 
泚以䞋、䟋では、 num_compデフォルトで暗黙的に指定されおいたす。テンプレヌト匕数のリストでの明瀺的な指瀺は省略されおいたす。 䞀般に、 insert操䜜を開発した埌、この方法でツリヌを構築する必芁はありたせん明瀺的な定矩。
説明したツリヌを図に瀺したす。

コンパむル時のデバッグに぀いお
定矩したクラスが意図したものを正確に蚘述しおいるこずをどのように確認したすか

議論ず研究のためのこの別個の興味深いトピックは、メタプログラムのデバッグです。 呌び出しスタック、倉数、いたいたしいprintf/std::coutはありたせん。 コンパむラ゚ラヌメッセヌゞ内に人間が読み取れる掚定型を出力できる手法がありたすが、原則ずしお、これは生成された構造をチェックするのにかなり圹立぀機胜ですたずえば、ツリヌを倉曎した埌。

単に誀ったプログラムの堎合は、マルチメガバむトの゚ラヌメッセヌゞの問題に觊れたせん緎習埌、これは問題ではなくなりたす。ほずんどの堎合、むンスタンス化の最初の゚ラヌのみがさらなる゚ラヌのカスケヌドに぀ながるためです。

しかし、逆説的に聞こえるかもしれたせんが、プログラムが正垞にコンパむルされたらどうでしょうか ここで、著者はデバッグ方法の遞択においおすでに自由です。 type_traits定矩されおいるようなメタ関数の結果の型は、 typeid(t).name()ずしお単玔にtype_traitsできたすC ++ 11以降、合法的にRTTIをスパむできたす。 単玔なデヌタ構造は、末尟再垰を備えた特別なメタ関数を䜿甚しお画面に衚瀺できたす。耇雑な構造の堎合、構造自䜓の操䜜ず「プリンタヌ」の耇雑さを同等にする必芁がありたす。

ツリヌラむブラリにはこのようなプリンタずその䜿甚䟋が含たれおおり、読者は蚘事の最埌にあるgithubぞのリンクでそれらに慣れるこずができたす。 たずえば、䞊蚘の䟋の印刷されたツリヌ
                   /-{2}  
          /-{3}-<
                   \-{4}  
 -{5} --- <
          \-{7}-\
                   \-{8}

身長


二分朚の高さを蚈算するための再垰関数を芋おみたしょう

  ///入力T-ツリヌ、出力h-高さ 
 
  高さT 
      IF T == NIL 
          結論0 
      その他 
          出力1 +最倧高さT.LEFT、高さT.RIGHT 
 

圌女は矎しい。 この関数をC ++に移怍しお移怍するだけです。

 ///    template <typename Tree> struct height; ///  : " T == NIL" template <> struct height<NIL> : std::integral_constant<size_t, 0> {}; ///    () template <typename Tree> struct height { static constexpr size_t value = 1 + max( height<typename Tree::LT>::value, height<typename Tree::RT>::value ); }; 
泚蚈算されたツリヌの高さは、数孊的な定矩よりも1倧きくなりたす空のセットの高さは定矩されおいたせん。 , 1 , height .
コヌドは非垞に簡単ですheight空のセットで呌び出すず、察応するstaticを含む特殊化がむンスタンス化されたすvalue = 0。そうでない堎合、むンスタンス化のカスケヌドは、NIL再垰が停止する空のサブツリヌ぀たり、同じサブツリヌに出䌚うたで続きたす。これは、C ++テンプレヌトを介しお再垰関数を実装する際の特城的な機胜です。再垰bottomを特殊化する必芁がありたす。特殊化ある時点で䞊蚘の関数を取埗できないTree::LT堎合Tree空のサブツリヌず等しくなりたすNIL。

䞊蚘のコヌドは関数を䜿甚しおいたすmax。それはする必芁がありconstexpr、シンプルでよく知られおいる実装の䟋たたはメタ機胜、そしお少し倉曎それを呌び出したす

 template <typename T> constexpr T max(T a, T b) { return a < b ? b : a; } 

最埌に、関数を䜿甚したすheight

 static_assert(height<t1>::value == 3, ""); 

関数の「耇雑さ」に぀いお話したしょう。n個のむンスタンス化がheight必芁です。nはツリヌノヌドの数です。むンスタンス化の深さはh-぀たり 朚の高さ。

これ以䞊に耇雑で、なぜここにあるのですか
, , , : (!), - , . , : , constexpr - variadic- .

: , gcc-5.4 « » ( ) 900 . , ( ). , height gcc, > 900. O - ( ), .

, C++14 ( std::integer_sequence ): N 0..N-1, , . , , ( , 30- 9000- ).

, (.. ) : . ( , ..), , — , — API , , — ( « 24 »!).


バむパス䞭心むンオヌダヌトラバヌサル


回避策は、特定の方法でノヌドたたはノヌドからのデヌタのリストを䜜成するこずです。これは、実際に重芁な甚語の問題です。䞭倮察称トラバヌサルは、巊右のサブツリヌの察応するトラバヌサルの結果の間でツリヌのルヌトが発生するトラバヌサルです。䞀緒に二分探玢朚の特性は、それは、二分探玢朚の呚りを䞭心に、我々が生成するこずを蚀いたす。䞍平等にし、理論を参照、ノヌドの゜ヌトされたリストクヌルを- 以前に定矩されたツリヌのトラバヌスは次のようになりたす。


再垰的な回避アルゎリズムは非垞に簡単です。

 ///入力T-ツリヌ、出力w-ノヌドのデヌタを順番にトラバヌスするリスト 
 
順序T
    IF T == NIL
        結論{}
    その他
        出力順T.LEFT+ {T.KEY} + INORDERT.RIGHT
 

この堎合の操䜜「+」は、リストの連結を意味したす。はい、タプルはコンパむル時に型リストであるため、タプルを連結するために䜿甚したものです。そしお再び-コヌドを取埗しお蚘述したす。

 ///    template <typename Tree> struct walk; ///  : " T == NIL" template <> struct walk<NIL> { using type = std::tuple<>; }; ///    () template <typename Tree> struct walk { private: //    using accL = typename walk<typename Tree::LT>::type; //   using accX = tuple_push_t<accL, typename Tree::type>; //      using accR = tuple_concat_t<accX, typename walk<typename Tree::RT>::type>; public: //  using type = accR; }; 

この堎合、明確にするためだけに、ロヌカルprivate゚むリアスを䜿甚したした。すべおの゚むリアスを盞互に眮き換えお、ランダムに生成された行の埌にスタッフィングを難読化するこずができ、むンデントでさえも保存されたせん。しかし、私たちは人々のために努力したすよね

関数の耇雑さwalkはOnむンスタンス化です。ここで、nはツリヌノヌドの数ですO衚蚘は簡略化に䜿甚されたす。正確なカりントでは、連結を考慮しお玄3nのメタ関数呌び出しが行われたす。機胜するこずをそれのnice tuple_concatずtuple_push1぀のむンスタンス化のために自分の仕事を行っお、圌らは再垰的ではありたせんので、タむプの可胜性の撀退に䌎うparameter packさん。䟋のように、むンスタンス化の深さはhにheight等しい -朚の高さ。

コヌドの可読性に぀いお
: , , .

: , , , . , : « ». , : , .


怜玢する


キヌ怜玢は、叀兞的なバむナリ怜玢ツリヌの䞻な操䜜です名前はそれ自䜓を衚しおいたす。キヌずデヌタを分離しないこずに決めたので、導入された比范噚を䜿甚しおノヌドを比范しCompたす。再垰的怜玢アルゎリズム

 ///入力T-ツリヌ、k-キヌタむプ、
///出力N-比范に関しおタむプk` == kを含むノヌド 
 
怜玢T、k
    T == NILたたはk == T.KEYの堎合
        結論T
    その他
        IF k <T.KEY
            出力怜玢T.LEFT、k
        その他
            結論の怜玢T.RIGHT、k
 

実装は、以前に開発された実装に䌌おいたす。

 ///    template <typename Tree, typename T> struct search; ///     template <typename T> struct search<NIL,T> { using type = NIL; }; ///    template <typename Tree, typename T> struct search { using Comp = typename Tree::comp; using type = typename std::conditional< Comp::template eq<T, typename Tree::type>::value, // k == T.KEY ? Tree, //  , : typename std::conditional< Comp::template lt<T, typename Tree::type>::value, // k < T.KEY ? typename search<typename Tree::LT, T>::type, //     typename search<typename Tree::RT, T>::type //  --   >::type >::type; }; 

それはもう少し耇雑に芋えたすが、偶然ではありたせんたず、アルゎリズム自䜓がより倚くのブランチより倚くの比范ず条件を含んでいたす、次に、先に定矩した順序関係を適甚し、それに基づいお、巊たたは右サブツリヌ。詳现に泚意しおください


そのようなむンプリメンテヌションの耇雑search再床- Onのむンスタンス、深さ- 時間朚の高さ。 「やめお」ず驚いた読者は「怜玢の察数的耇雑さなどはどうだろう」ず叫ぶず

、石が暪に飛んでstd::conditional、挔算子ずの根本的な違いが明らかになりたすか ::䞉項挔算子は評䟡されない匏を評䟡したせんその結果たずえば、良心で、挔算子の最初の匕数でこのポむンタヌをチェックするずきに砎棄される2぀の匏のいずれかでnullポむンタヌを逆参照できたす。std::conditionalたた、すべおの3぀の匕数をむンスタンス化する理由は䞀぀だけである通垞のメタ関数などを、std::conditional再垰を停止するのに十分ではなく、再垰の底を特殊化する必芁がありたす。

盎接の結果は、根から葉たでツリヌ党䜓をキャプチャする䜙分なむンスタンス化です。特別な゜ヌサリヌでは、別のレベルの間接化を远加しお、未知のノヌドがたったくないサブツリヌのパスに沿ったむンスタンス化の再垰の各ステップで「オフ」にしこのレベルの間接化に特化した束を䜜成するこずにより、しかし、Ohの倧切な耇雑さを達成できたす私の意芋では、これはより深い研究のためのタスクであり、この堎合は時期尚早な最適化になりたす。

アプリケヌションの䟋䜿甚された゚むリアステンプレヌト。他の䟋に぀いおはリポゞトリを参照

 using found3 = search_t<NIL, num_t<0>, num_comp>; //    using found4 = search_t<t1, num_t<5>, num_comp>; //    using found5 = search_t<t1, num_t<8>, num_comp>; //    static_assert(std::is_same<found3, NIL>::value, ""); //   static_assert(std::is_same<found4, t1>::value, ""); //   static_assert(std::is_same<found5, leaf<num_t<8>>>::value, ""); //  

これは奇劙に思えるかもしれたせん私たちは、実際に匕数ずしお既に指定されおいるタむプのノヌドを探しおいたす-なぜですか実際、これには珍しいこずは䜕もありたせん- 比范の芳点から匕数に等しい型を探しおいたす。 STLは朚std::mapものノヌドに栌玍されおいるペアstd::pair、及び䞀察の第䞀郚材があるず考えられおいるキヌ実際には、比范に関䞎したす。ツリヌに同じものを保存し、比范噚にペアの最初のタむプのペアをstd::pair比范させるだけで十分ですComp-そしお、叀兞的な連想メタコンテナを取埗したす蚘事の最埌でこの考えに戻りたす。

挿入


メタ機胜の助けを借りおツリヌを構築する方法を孊ぶずきです以前ず同じように、手でツリヌを描くこずはすべお完了しおいたせんか。再垰挿入アルゎリズムは、新しいツリヌを䜜成したす。

 ///入力T-ツリヌ、k-挿入するキヌタむプ、
///出力T '-芁玠が挿入された新しいツリヌ 
 
INSERTT、k
    IF T == NIL
        結論{NIL、k、NIL}
    その他
        IF k <T.KEY
            結論{挿入T.LEFT、k、T.KEY、T.RIGHT}
        その他
            結論{T.LEFT、T.KEY、INSERTT.RIGHT、k}
 

圌の仕事を説明したしょう挿入が発生するツリヌが空の堎合、挿入された芁玠は新しいツリヌ{NIL、k、NIL}を䜜成したす。この芁玠を持぀シヌト再垰䞋郚。ツリヌが空でない堎合、空のツリヌに再垰的に倱敗し぀たり、巊たたは右のサブツリヌが空になるたで、最埌にこのサブツリヌで同じシヌト{NIL、k、NIL}を圢成する必芁がありたすNIL、新しい巊たたは右のサブツリヌの圢で「ハング」する途䞭。型の䞖界では、既存の型を倉曎するこずはできたせんが、新しい型を䜜成するこずはできたす-これは再垰のすべおのステップで発生したす。実装

 template <typename Tree, typename T, typename Comp = typename Tree::comp> struct insert; ///   template <typename T, typename Comp> struct insert<NIL,T,Comp> { using type = leaf<T,Comp>; }; ///   template <typename Tree, typename T, typename Comp> struct insert { using type = typename std::conditional< Comp::template lt<T, typename Tree::type>::value, // k < T.KEY? node<typename Tree::type, //   {INSERT(T.LEFT,k), T.KEY, T.RIGHT} typename insert<typename Tree::LT, T, Comp>::type, typename Tree::RT, Comp>, node<typename Tree::type, //   {T.LEFT, T.KEY, INSERT(T.RIGHT,k)} typename Tree::LT, typename insert<typename Tree::RT, T, Comp>::type, Comp> >::type; }; 

空のツリヌに芁玠を远加するには、コンパレヌタヌを明瀺的に指定する必芁がありたすComp。ツリヌが空でない堎合、コンパレヌタはデフォルトでこのツリヌのルヌトから取埗されたす*。

この挿入の耇雑さは次のずおりです。Onむンスタンス化nは既存のノヌドの数、再垰の深さはhhはツリヌの高さです。明瀺的な䜿甚䟋

 using t2 = leaf<num_t<5>, num_comp>; using t3 = insert_t<t2, num_t<3>>; using t4 = insert_t<t3, num_t<7>>; static_assert(height<t4>::value == 2, ""); //  2  using t5 = insert_t<insert_t<insert_t<t4, num_t<2>>, num_t<4>>, num_t<8>>; static_assert(std::is_same<t5, t1>::value, ""); //    

ラむブラリにはメタinsert_tupleタむプの実装があり、これにより、タむプのタプルをすぐにツリヌに入れるこずができたすボンネットの䞋ではinsert、タプルの単なる再垰です。

 using t6 = insert_tuple_t<NIL, std::tuple< num_t<5>, num_t<7>, num_t<3>, num_t<4>, num_t<2>, num_t<8> >, num_comp>; static_assert(std::is_same<t1, t6>::value, ""); 

バむパス幅幅優先トラバヌサル


二分朚の幅を暪断するたたはグラフ理論から幅を探玢するず、ノヌドのリストが「レベル」順に衚瀺されたす。最初にルヌト、次に深さ1のノヌド、次に深さ2のノヌドなどが衚瀺されたす。このようなバむパスのアルゎリズムは、スタックではなくノヌドのキュヌを䜿甚しおさらに出力するため、再垰的に「倉換」するこずは困難です。ネタバレの䞋で、興味のある読者は回避策を芋぀けるでしょう。ここでは、幅をクロヌルするこずで「解析」されたツリヌを、クロヌル結果のタプルから芁玠のたったく同じシヌケンシャル挿入にアセンブルできるずいう有甚な事実のみに泚目したす。この図は、テストツリヌの幅の呚りを歩いおいたす。


再垰的な幅のりォヌク
: 0 h . :

 /// : T - , l -   ,
/// : t -     
COLLECT_LEVEL(T,l):
     T == NIL
         {}
    
         l == 0
             {T.KEY}
        
             COLLECT_LEVEL(T.LEFT,l-1) + COLLECT_LEVEL(T.RIGHT,l-1)

. , O(nh) - (, , collect_level ).


取り倖し


ノヌドの削陀は重芁なタスクです。叀兞的なアプロヌチでは、3぀のケヌス子ノヌドの数によるが考慮され、埌続ノヌドず芪ノヌドの抂念がアルゎリズムで䜿甚されたす。芪ノヌドぞのバックリンクがない堎合「父芪ず子䟛に぀いお」のネタバレ参照、アルゎリズムを効果的に実装するのは問題です。ツリヌを登る各操䜜にはOnの耇雑さがありたす。このような削陀の単玔な実装は、むンスタンスの数の点で「組み合わせの爆発」を匕き起こしたす耇雑さはOn n付近です。ノヌドを削陀するためのメタ関数の開発は、ラむブラリを改善するためのさらなる蚈画の䞀郚です。蚘事の最埌にあるUPD2を参照しおください。

申蟌み


息を吞っお、最埌に質問に泚意を払いたしょう。なぜバむナリコンパむル時の怜玢ツリヌが必芁なのでしょうか3぀の答えがありたす

非建蚭的
*トロリヌバスのある写真*。省略したす。

明らか
...研究に興味のある人向け

構成的
この構造の可胜なアプリケヌションの䟋を次に瀺したす。


:
«Modern C++ Design: Generic Programming and Design Patterns Applied» ( « ++» , . ) : « ». static_assert , C++11, ( , policy ), STL, C++98 (« ») ( , , , ).

, : compile-time — , ( , ), ( «», , - ).

締めくくりに、ツリヌの開発ず蚘事の執筆に぀ながったタスクに぀いお、少しお話したいず思いたす。正芏衚珟をDFA決定論的有限状態機械に盎接倉換するためのアルゎリズムがいく぀かあり、それを認識したす。そのいく぀かは、いわゆる 構文ツリヌ、その性質䞊、「䜕もない」バむナリです。したがっお、コンパむル時怜玢のバむナリツリヌは、コンパむルおよびDFAのフラットコヌドに展開できる埋め蟌み埌の正芏衚珟のコンパむル時パヌサヌの䞍可欠な郚分および実装のための単なる興味深い構造であり、これは別のプロゞェクトの䞀郚になりたす。

それで䜕


ささやき冬...
, (, , ) . — , (, - ), std - ( — tutorial ). , .

!

文孊



→ リポゞトリ

UPD
std::conditional:
izvolov std::conditional . search O(h) :
 template <typename Node, typename T, typename Comp> struct search { using type = typename std::conditional< Comp::template eq<T, typename Node::type>::value, Node, typename search<typename std::conditional< Comp::template lt<T, typename Node::type>::value, typename Node::LT, typename Node::RT >::type, T, Comp>::type >::type; }; 
(+ insert ) .


UPD2
remove insert search O(h):
, insert search remove O(h) ( insert ). SFINAE + decltype ( , . ). remove :
 template <typename Tree, typename T> struct remove { private: enum : bool { is_less = Tree::comp::template lt<T, typename Tree::type>::value }; enum : bool { is_equal = Tree::comp::template eq<T, typename Tree::type>::value }; enum : size_t { children = children_amount<Tree>::value }; using key = typename min_node_t< typename std::conditional< is_equal && children == 2, typename Tree::RT, leaf<typename Tree::type, typename Tree::comp> >::type >::type; using recursive_call = typename remove< typename std::conditional< is_less, typename Tree::LT, typename std::conditional< !is_equal || children == 2, typename Tree::RT, NIL >::type >::type, typename std::conditional< is_equal, key, T >::type >::type; static typename Tree::RT root_dispatcher(...); template <typename Bush> static typename std::enable_if< sizeof(typename Bush::LT::type), typename Bush::LT >::type root_dispatcher(Bush&&); public: using type = typename std::conditional< is_equal && (children < 2), decltype(root_dispatcher(std::declval<Tree>())), node< key, typename std::conditional< is_less, recursive_call, typename Tree::LT >::type, typename std::conditional< !is_less, recursive_call, typename Tree::RT >::type, typename Tree::comp > >::type; }; 
, , . , , , , .

Source: https://habr.com/ru/post/J320686/


All Articles