古き良きCRTPの新しいトリックを学ぶ

時には、その価値が非常に疑わしいことをします。 これはまさにそうです。



コードは千語よりも優れています



猫の尻尾を引っ張るつもりはありませんが、ポイントまでまっすぐに行きます。 通常、次のようにCRTPを使用します。

template<typename type, typename tag> struct Base {}; template<typename type, typename tag> void f(const Base<type, tag>&) {} struct Foo : Base<Foo, void> {}; int main() { Foo foo; f(foo); }
      
      





f()



関数は、引数がどのタグを持っているかを実際に気にしません。そして、 Base



から継承された任意のタイプのオブジェクトを取ります。 しかし、興味のないタグを省略した方が便利だと思いませんか? それをチェックしてください:

 template<typename t> struct base_tag { typedef decltype(get_tag((t*)42)) type; }; template<typename type, typename tag = typename base_tag<type>::type> struct Base { friend tag get_tag(type*); //never defined }; template<typename type> void f(const Base<type>&) {} struct Foo : Base<Foo, void> {}; int main() { Foo foo; f(foo); }
      
      





ここで、 f()



宣言を見ると、関数が引数のタグを実際に気にしていないことを直感的に理解できます。



仕組み



Base



クラスでは、タグを返し、継承された型へのポインターを取るフレンド関数が宣言されます。 この関数は定義する必要がないことに注意してください。 Foo



などの型を定義する場合、対応するプロトタイプを使用して関数を実際に宣言します。この場合は次のようになります。

 void get_tag(Foo*);
      
      







テンプレートのインスタンスの作成中にf()



を呼び出すと(テンプレートのインスタンス化)、コンパイラーは関数引数( Foo



クラスのオブジェクト)のデフォルトのテンプレート引数を決定しようとします。

  1. コンパイラは、 base_tag



    テンプレートインスタンスからタグタイプを取得します。
  2. 次に、ポインターFoo*



    を引数として、 get_tag()



    返す型として定義されます。
  3. オーバーロード解決メカニズムをトリガーし、テンプレート引数としてFoo



    型とvoid



    型を持つBase



    クラスで宣言された関数、つまりBase<Foo, void>



  4. ???
  5. 利益!


つまり、円は閉じられています!



ECRTP



より良いものを発明することなく、私はそれを「過度に好奇心が強い」繰り返しテンプレートパターンと呼んでいます。 それで、他に何ができますか?



これが本当に必要な場合は、タグを明示的に指定できます。

 template<typename type> void g(const Base<type, int>&) {} struct Bar : Base<Bar, int> {}; int main() { Foo foo; Bar bar; f(foo); f(bar); g(foo); //doesn't compile by design g(bar); }
      
      







引数タグはint



型でなければならないため( Foo



void



型であるためg(foo)



g(foo)



は意図的にコードをコンパイルすることを許可しないことに注意してください。 この状況では、コンパイラは美しいエラーメッセージを生成します。 まあ、少なくともMSVC10とGCC4.7。



MSVC10
 main.cpp(30):エラーC2784: 'void g(const Base <type、int>&)':テンプレート引数を推定できませんでした
           「Foo」の「const Base <type、int>&」
           main.cpp(18):「g」の宣言を参照


Gcc4.7
 source.cpp:関数 'int main()'内:
 source.cpp:30:8:エラー: 'g(Foo&)'の呼び出しに一致する関数がありません
 source.cpp:30:8:注:候補は:
 source.cpp:18:6:注:テンプレート<class type> void g(const Base <type、int>&)
 source.cpp:18:6:注:テンプレート引数の推論/置換に失敗しました:
 source.cpp:30:8:注:タイプ「int」と「void」の不一致
 source.cpp:30:8:注:「Foo」は「const Base <type、int>」から派生したものではありません
MSVCよりも優れています!




デフォルトタグを設定することもできます:

 template<typename type> void get_tag(type*); //default tag is 'void' template<typename t> struct base_tag { typedef decltype(get_tag((t*)42)) type; }; template<typename type, typename tag = typename base_tag<type>::type> struct Base { friend tag get_tag(type*); //never defined }; struct Foo : Base<Foo> //tag defaults to void {};
      
      







上記の定義は同等です

 struct Foo : Base<Foo, void> {};
      
      







そのため、タグがまったくないと想定し、この機能を高度な使用のために残すことができます。



C ++ 98はどうですか?



古いコンパイラはdecltype



キーワードをサポートしていません。 ただし、タグの数(または何でも)が限られている場合は、 sizeof



sizeof



trick)でトリックを使用できます。

 struct tag1 {}; //a set of tags struct tag2 {}; struct tag3 {}; #define REGISTER_TAG(tag, id) char (&get_tag_id(tag))[id];\ template<> struct tag_by_id<id>\ { typedef tag type; }; template<unsigned> struct tag_by_id; REGISTER_TAG(tag1, 1) //defines id's REGISTER_TAG(tag2, 2) REGISTER_TAG(tag3, 42) template<typename t> struct base_tag { enum { tag_id = sizeof(get_tag_id(get_tag((t*)42))) }; typedef typename tag_by_id<tag_id>::type type; }; template<typename type, typename tag = typename base_tag<type>::type> struct Base { friend tag get_tag(type*); //never defined }; template<typename type> void f(const Base<type>&) {} struct Foo : Base<Foo, tag1> {}; int main() { Foo foo; f(foo); }
      
      





少し冗長ですが、動作します。



余分な体の動き?



それで、これらすべての余分なジェスチャーは本当にですか?



この手法により、コードがもう少し美しくなることは既に確認しました。 2つの引数の場合に何が起こるか見てみましょう。 もちろん、次のようなコードを書くことができます。

 template<class type1, class tag1, class type2, class tag2> void h(const Base<type1, tag1>&, const Base<type2, tag2>&) {}
      
      





class



キーワードを短くしても、コードが大幅に短くなることはありません。



これと比較してください:

 template<class type1, class type2> void h(const Base<type1>&, const Base<type2>&) {}
      
      





タグ? いいえ、聞いていません...



あなたは3つ以上の引数を自分で素晴らしい状況を想像することができます。



アイデアはこれです:特定のことに興味がない場合、それは必ずしも明示的である必要がありますか? 誰かがstd::vector, ( ), " std::vector () std::allocator



". , (, ), . , , . , , .





, , - .








書くときstd::vector, ( ), " std::vector



() std::allocator



". , (, ), . , , . , , .





, , - .




std::vector, ( ), " std::vector



() std::allocator



". , (, ), . , , . , , .





, , - .




std::vector, ( ), " std::vector



() std::allocator



". , (, ), . , , . , , .





, , - .




std::vector, ( ), " std::vector



() std::allocator



". , (, ), . , , . , , .





, , - .







All Articles