3.12 タイプリストの部分的な並べ替え Resu1t は T となる。 さもなければ : TList : :Tai1 と T で MostDerived を適用し , 候補 (Candidate TList: :Head が候補 (candidate) から導出されている場合 : Resu1t を TList: :Head とする。 さもなければ : Resu1t を候補 (candidate) とする。 MostDerived の実装は以下のようになります : template く class TList , class T> struct MostDerived; 69 を取得する。 template く class T> struct MostDerived く Nu11Type , T> typedef T Resu1t ; template く class Head, class Tai1, struct MostDerived く Type1ist く Head, private : class T> Tai1> , T> typedef typename MostDerived く Tai1 , T> : :Resu1t Candidate ; public : typedef typename Se1ect く SUPERSUBCLASS (Candidate , Head) , Head, Candidate> : :Resu1t Resu1t ; DerivedToFront アルゴリズムは , プリ DerivedToFront の実装です : ミテイプとして MostDerived を使用します。以下が template く class T> struct DerivedToFront ; template く > struct DerivedToFront く Nu11Type> typedef Nu11Type Resu1t ; template く class Head, class Tail> struct DerivedToFront く Type1ist く Head, private : typedef typename MostDerived く Tai1 , TheMostDerived; typedef typename Rep1ace く TaiI , TheMostDerived, Head> : : Resu1t L; Tai1> > Head> : : Resu1t
3.7 タイプリストの検索 出力 : コンパイル時の内部定数 value TList が NuIIType である場合 : value は一 1 となる。 さもなければ : TList の先頭が T である場合 : value は 0 となる。 さもなければ : TList の先頭要素を取り除いたものと T で工 ndex0f を適用し , 結果を一次的な値 temp に格納 する。 temp がー1 である場合 : value は一 1 となる。 さもなければ : value は 1 と temp を加えた値となる。 61 template く class TList , class T> struct lndexOf ; その実装です : す。最後の分岐 (temp から value を計算する部分 ) は条件演算子 ? を用いた数値計算となります。以下が せるためには , ちょっとした注意が必要です。アルゴリズム中の各分岐に対応して 3 つの特殊化が必要となりま Index0f は , 比較的単純なアルゴリズムです。しかし「発見できない」場合の値 ( ー 1 ) を結果として伝播さ template く class T> struct 工 ndexOf く Nu11Type , T> enum { value template く class T, class Tai1> struct 工 ndexOf く TypeList く T, Tai1> , enum { value = 0 } ; template く class Head, class Tai1, T> class T> struct 工 ndexOf く TypeIist く Head, Tai1> , T> private : enum public : enum { temp value IndexOf く TaiI , T> : :value } ; temp 1 + temp } ;
64 typedef Nu11Type ResuIt ; template く class T, class TaiI> struct Erase く Type1ist く T, Tai1> , typedef TaiI ResuIt ; T> 第 3 章 / / 特殊化 2 / / 特殊化 3 タイプリスト template く class Head, class Tai1 , class T> struct Erase く Type1ist く Head, Tai1> , T> typedef Type1ist く Head, typename Erase く Tai1 , T> : : Resu1t> Resu1t ; TypeAt のケースと同様 , デフォルトとなるテンプレートは存在しません。これは , 特定の型に対してしか Erase の実体化を行うことができないことを意味しています。例えば , Erase く double, int > に適合する特殊 化は存在しないため , コンパイル時にエラーとなります。 Erase の第 1 パラメータには , タイプリストを指定し なければならないわけです。 SignedTypes の定義を用いると , 以下のような記述が可能になります : / / SomeSignedTypes には TYPELIST-6 (signed char , short int , / / int , long int , double , long double) と等価なものが保持 / / されます。 typedef Erase く SignedTypes , float> : : Resu1t SomeSignedTypes ; では , 再帰型の削除アルゴリズムに取り組んでみましよう。 EraseA11 テンプレートは , タイプリスト中に存 在する指定された型の要素全てを削除します。実装は 1 点を除けば E て ase と似た形になります。つまり削除す る型を検出しても , このアルゴリズムは停止しないのです。 EraseA11 は , 自分自身を再帰的に適用してリスト の末尾まで判定を続行し , 適合したものを削除するわけです : template く class TList , class T> struct EraseA11 ; template く class T> struct EraseAII く Nu11Type , T> typedef Nu11Type Resu1t ; template く class T, class Tai1> struct EraseAII く Type1ist く T, TaiI> , T> / / リストの最後まで進めながら , 型の削除を行う typedef typename EraseA11 く Tai1 , T> : : Resu1t ResuIt ;
62 3.8 タイプリストへの連結 入力 : タイプリスト TList, 型あるいはタイプリスト T 第 3 章 タイプリスト タイプリストに型やタイプリストを連結する手段も必要でしよう。前述したように , タイプリストの変更を行 うことはできないため , 結果を保持したまったく新しいタイプリストを作成することによって「値によるリター ン」を実現することになります。 Append 出力 : 内部型定義 Resu1t TList が Nu11Type であり , かっ T も Nu11Type である場合 : Resu1t は Nu11Type となる。 さもなければ : TList が Nu11Type であり , かっ T が単一の型 ( 非タイプリスト ) である場合 : template く > struct Append く Nu11Type , Nu11Type> template く class TList , class T> struct Append; このアルゴリズムをそのまま実現すると , 以下のようになります : るタイプリストとなる。 ResuIt は TList: :Head を先頭要素 , 以降は T を TList: :Tai1 に Append した結果からな TList が非 null である場合 : さもなければ : Resu1t は T 自身となる。 TList が Nu11Type であり , かっ T がタイプリストである場合 : さもなければ : Resu1t は T を唯一の要素として保持したタイプリストとなる。 typedef Nu11Type Resu1t ; template く class T> struct Append く Nu11Type , typedef TYPEL 工 ST—I (T) Resu1t ; template く class Head, class Tail> T> struct Append く Nu11Type , Type1ist く Head, Tail> > typedef Type1ist く Head, Tai1> Resu1t ;
3.10 重複の削除 template く class Head, class Tail, class T> struct EraseA11 く Type1ist く Head, Tai1> , T> / / リストの最後まで進めながら , 型の削除を行う typedef Type1ist く Head, typename EraseA11 く Tai1 , T> : :Result> Resu1t ; 3.10 重複の削除 タイプリスト中の重複した型を削除するという機能も必須です。 65 タイプリスト中にそれぞれの型が一度しか現れないよう , タイプリストを変換するというのが要求です。例 えば : TYPELIST_6 (Widget , Button, Widget , TextFie1d, Scr011Bar , Button) これを以下のようにするわけです : TYPEL 工 ST_4(Widget , Button, TextFieId, ScroIIBar) この処理はちょっと複雑ですが , Erase を用いて実現することができます。 NoDuplicates 入力 : タイプリスト TList 出力 : 内部型定義 Resu1t TList が NuIIType である場合 : Resu1t は Nu11Type となる。 さもなければ : TList: :Tai1 に対して NoDup1icates を適用し , 一時的なタイプリスト LI を取得する。 LI と TList::Head に対して Erase を適用する。結果として L2 を取得する。 Resu1t は TList : : Head を先頭要素 , 以降は L2 からなるタイプリストとなる。 このアルゴリズムを実装したコードは , 以下の通りです : template く class TList> struct NoDup1icates; template く > struct NoDup1icates く Nu11Type> typedef Nu11Type Resu1t ; template く class Head, class Tai1> struct NoDup1icates く Type1ist く Head, Tai1> >
36 第 2 章テクニック セクション 2.4 で考察した NiftyContainer の例では , バックエンドの格納領域として std: :vector を用い たいという要求が出てくるかもしれません。この場合 , ポリモフィズム型には値を格納できず , ポインタを格納 しなければならないことは明らかです。その一方で , 効率を考慮して , 非ポリモフィズム型には値を格納したい という要求も出てくるでしよう。 以下のクラス・テンプレートの場合 : template く typename T, b001 isP01ymorphic> class NiftyContainer vector く T*> (isPoIymorphic カゞ true の場合 ) , あるいは vector く T> (ispolymorphic カゞ false の場合 ) の いずれかを格納しなければならないわけです。突き詰めれば , ispolymorphic の値に応じて Va1ueType ( 値の 型 ) を typedef することによって , T * や T にしなければならないのです。 これには , 以下のような特性 ( な ) クラス・テンプレート (Alexandrescu2000a) を用いることができ ます : template く typename T, b001 isP01ymorphic> struct NiftyContainerVaIueTraits typedef T* Va1ueType ; template く typename T> struct NiftyContainerVaIueTraits く T, typedef T Va1ueType ; false> template く typename T, b001 isPolymorphic> class NiftyContainer typedef NiftyContainerVa1ueTraits く T, isP01ymorphic> Traits ; typedef typename Traits : : Va1ueType Va1ueType ; しかし , この方法は不格好です。そして , スケーラビリテイもありません。型の選択を追加する度に , 新たな特 性クラス・テンプレートを定義しなければならないからです。 Loki では , ライプラリ・クラス・テンプレート select を採用し , その場での型の選択を可能にしています : template く b001 flag, typename T, typename U> struct Se1ect typedef T ResuIt ; template く typename T, typename U>
16 第 1 章ポリシーを基にしたクラス・デザイン 造となるわけです。このような方法で SmartPtr を設計することにより , ユーザが簡単な typedef を使用する 2 つのポリシーは , 以下のような形で定義できるでしよう : SafeWidgetPtr ; typedef SmartPtr く Widget , EnforceNotNu11 , SingIeThreaded> 同じアプリケーション中で , いくつかのスマート・ポインタ・クラスを定義し , 使用することもできます : WidgetPtr; typedef SmartPtr く Widget , NOCheck , SingIeThreaded> ことによって SmartPtr を設定できるようになるわけです : ポリシー・クラス NoCheck, および EnforceNotNu11 の実装例は以下のようになります : 列化されます。 コンストラクタは T & を受け取らなければなりません。 L 。 ck オプジェクトの生存期間中は , T に対する操作が直 スレッド・モデル : クラス・テンプレート ThreadingMode1 く T> は , Lock と呼ばれる内部型を公開し , その 照を外す前に , それを引数として c eck を呼び出します。 出すことができるメンバ関数 Check を公開しなければなりません。 smartptr は , 指しているオプジェクトの参 参照を外す前のチェック : クラス・テンプレート CheckingP01icy く T> は , 型 T * の左辺値を引き渡して呼び if ( ! ptr) throw Nu11PointerException() ; static VOid Check()* ptr) class Nu11PointerException : public std: : exception { template く class T> struct EnforceNotNu11 static void Check(T*) { } template く class T> struct NoCheck template Checking ポリシーを使用した smartptr は , 以下のようになります : if ( !ptr) ptr = GetDefau1tVa1ue ( ) ; static void Check(T*& ptr) template く class T> struct EnsureNotNu11 することさえできるようになります : ます。また , 以下のようにポインタへの参照を受け取って , 指しているオプジェクトをデフォルト値へと初期化 様々なチェック・ポリシーをプラグ・インすることによって , 様々な動作を実装することができるようになり
2.1 コンパイル時の表明 27 使用しているシステムのポインタが char よりも大きい場合 , 長さゼロの配列生成を試みようとするため , コン パイラによってエラーが通知されるはずです。 このアプローチが抱える問題は , 通知されるメッセージがまったくと言っていいほど意味の無いものである点 です。「長さゼロの配列を生成することはできません」といったメッセージから「 char 型はポインタを保持する には小さすぎます」という意味を汲み取ることはできないでしよう。可搬性のある形でエラー・メッセージを提 供するのは非常に難しいことです。工ラー・メッセージについては遵守すべき規則というものが無く , 完全にコ ンパイラまかせになっているのです。例えば , 工ラーが未定義変数を参照している場合であっても , その変数の (Compi1eTimeError く (expr) ! = 0 > ( ) ) #define STATIC—CHECK(expr) \ template く > struct Compi1eTimeError く true> { } ; template く b001> struct Compi1eTimeError ; せん : で運が良ければ , コンパイラはエラー・メッセージ中に該当テンプレートの名前を表示してくれるかもしれま 少しましな解決策として , 参考になるような名前を付けたテンプレートを用いる方法が考えられます。これ 名前がエラー・メッセージ中に表示されるとは限らないのです。 も真となることは保証されていません。 ) では , 以下のコードを記述した場合に何が起こるのかを考えてみま こでは , sizeof(char) く sizeof(void*) が成立すると仮定しています。 ( 規格によれば , これが必ずし (void) sizeof (Compi1eTimeChecker く (expr) ! = 0 > ( (ERROR-##msg() ) ) ) ; \ class ERROR-##msg { } ; \ #define STATIC—CHECK(expr, msg) \ template く > struct Compi1eTimeChecker く false> { } ; CompiIeTimeChecker( . template く b001> struct Compi1eTimeChecker より判りやすいでしよう : 合 , Compi1eTimeError という名前は , もはや判りやすいものではありません。 compi1eTimeChecker の方が , という制限が残ります。この改良版 c 。 mpi1eTimeErr 。 r は以下のようなコードとなります。このようにした場 ・メッセージは C+ + の正当な識別子 ( 空白は使用できない , 数字で開始できない等 ) でなければならない , ラメータを表示させることができないか考えてみましよう。この手法では , 引き渡しを行うカスタマイズ版工 ないでしようか ? STATIC-CHECK に追加のパラメータを引き渡し , 何とかしてエラー・メッセージ中にそのパ もちろん , まだまだ改善する余地は残されています。こういったエラー・メッセージのカスタマイズはでき ものであり , コンパイラやプログラムのバグでは無いということが判断できます。 いったメッセージがコンパイラによって表示されるわけです。このメッセージによって , 該当工ラーが意図的な Compi1eTimeError く false> の実体化を試みた場合 , 「 Compi1eTimeError く false> は未定義の特殊化です」と Compi1eTimeError は , プーリアン定数の true という値に対してしか定義されていません。このため , Compi1eTimeError は , 型以外のパラメータ ( プーリアン定数 ) を受け取るテンプレートです。しかし
3.9 タイプリストからの型の削除 template く class Head, class Tai1, class T> struct Append く Type1ist く Head, Tai1> , T> typedef Type1ist く Head, typename Append く Tai1 , T> : : Result> Resu1t ; 63 部分的な特殊化を行っている Append の最後の定義では , リストの先頭要素を取り除いたタイプリストおよび連 結する型を引き渡して , Append テンプレートを再帰的に実体化している点に注意して下さい。 これで , 単一の型とタイプリストのいずれに対しても適用可能な Append 操作が実現できました。以下のス 3.9 タイプリストからの型の削除 これによって 0 + における全ての符号付き数値型を保持したリストを定義することができます。 SignedTypes ; TYPELIST_3 (float , double , long double)> : :ResuIt typedef Append く SignedIntegra1s , テートメント : ますは , 型が一致した最初の要素のみを削除する場合を考えてみましよう。 一致した最初の要素のみを削除するか , 型が一致した全ての要素を削除するかです。 次は逆の操作ータイプリストからの型の削除です。これには 2 種類の処理形態が考えられます。つまり , 型が 以下が , このアルゴリズムを 0 + で実現したものです : イプリストとなる。 Resu1t は TList: :Head を先頭要素 , 以降は TList: :Tai1 から T を Erase した結果からなるタ さもなければ : Resu1t は TList: :Tai1 となる。 T が TLi st : : Head と同じである場合 : さもなければ : ResuIt は Nu11Type となる。 TList が Nu11Type である場合 : 出力 : 内部型定義 Resu1t 入力 : タイプリスト TList, 型 T Erase template く class TList, template く class T> struct Erase く Nu11Type , c1ass T> struct Erase ; / / 特殊化 1 T>
66 private : typedef TypeIist く Head, L2> ResuIt ; public : typedef typename Erase く LI , Head> : :Resu1t L2 ; typedef typename NODup1icates く Tai1> : :Resu1t LI ; 第 3 章 タイプリスト 一見すると EraseA11 を適用した方が良さそうな部分で Erase が用いられています。これは何故でしよう か ? 私たちがやりたいことは重複した型を全て取り除きたいのでしたよね ? これは , Erase が NoDup1icates に対する再帰後に適用されるからです。つまり , 既に重複の無くなったリストから指定した型を削除するため , 削除される型の実体は高々 1 つしか現れないということが保証されているのです。再帰プログラミングの奥の深 さを感じていただけたでしようか。 3.11 タイプリスト中にある要素の置換 削除の代わりに置換が必要となる場合が時折あります。セクション 3.12 で見ていただくことになりますが , 型を別な型で置き換えるという機能は , 洗練されたイディオムを実現するための重要な道具となるのです。 タイプリスト TList 中の型 T を型 U で置換するというのが要求です。 Replace 入力 : タイプリスト TList, 型 T ( 置換されるもの ) , 型 U ( 置換するもの ) 出力 : 内部型定義 Resu1t TList カゞ NuIIType である場合 : Resu1t は Nu11Type となる。 さもなければ : タイプリスト TList の先頭が T である場合 : Resu1t は U を先頭要素 , 以降は TList : : Tai1 からなるタイプリストとなる。 さもなければ : Resu1t は TList: :Head を先頭要素 , 以降は TList: :Tai1, T, U に対して Rep1ace を適用した 結果からなるタイプリストとなる。 正しい再帰アルゴリズムを見つけだすことができれば , コードは記述できたも同然です : template く class TList, class T, class U> struct Rep1ace; template く class T, class U> struct Rep1ace く Nu11Type , T , U> typedef Nu11Type ResuIt ; template く class T, class Tai1, class U> struct RepIace く Type1ist く T, TaiI> , T, U>