記事は2つの部分で構成されます。 最初の部分では、問題について説明し、最初のわずかに曲がった解決策を示します。 2番目の部分は、概説したソリューションの改善と一般化に専念します。
最初のステップは、問題を説明することです。 ヘッダーファイルで関数テンプレートを宣言しているとします。 テンプレートが可能なすべてのものに潜在的に適している必要がある場合は、ヘッダーファイルでここで定義する必要があります。 これには、エンドツーエンドの依存関係、コンパイル時間の増加、およびヘッダーファイルの欠落が伴います。 しかし、これは依然として避けられません。 もちろん、別のヘッダーファイルでテンプレートを定義し、広告ファイルの下部に含めることができます。 これにより、最初の2つではなく、3番目の問題が解決されます。 現在、逆の状況は、テンプレートをいくつかの特定のタイプにのみ使用(インスタンス化)する必要がある場合です。 その後、定義をソースに安全に転送し、個々のタイプごとにテンプレートを明示的にインスタンス化できます。 少し時間のかかる伴奏ですが、ヘッダーのがらくたよりはましです。
私たちの状況はその中間です。 関数テンプレートがあり、特定のタイプのリスト用にインスタンス化する必要があります。これは、typedefを使用してプロジェクト内のどこかに不変です。 さて、例えば:
typedef TypeList<int,char,bool,string, EmptyList> MyTypeList.
このようなタイプのリストは、「C ++でのモダンデザイン」のA. Alexandrescuから読むことができ、実装例はこちらです。
カットの下で、自作の実装(おそらく他の何千ものと同じ)。 私は個人的にそれが好きです
typedef TypeList<int,char,bool,string, EmptyList> MyTypeList;
古典的な録音の代わりに
typedef TypeList<int,TypeList<char,TypeList<bool,TypeList<string, EmptyList>>>> MyTypeList;
struct EmptyList{}; template<typename Head, typename... Tail> struct TypeList { typedef Head head_t; typedef TypeList<Tail...> tail_t; }; template<typename Head> struct TypeList<Head, EmptyList> { typedef Head head_t; typedef EmptyList tail_t; };
トピックに戻ります。 プロジェクトには50種類の関数テンプレートを含めることができ、各テンプレートは、この遍在するタイプのリストに対してのみインスタンス化する必要があります。 より良いこと:
1)ヘッダーファイルでテンプレートを定義することは、敗北主義的な態度です。
2)ソースでテンプレートを定義し、リストのすべてのタイプに手動で特化します...ええ、リストが拡大、縮小、または変更された場合は、50箇所で修正します。
両方のオプションが悪いです。 この記事の目的は、ソースでテンプレートを定義し、各タイプの手動インスタンスを取り除く方法を示すことです。 目標を少し明確にし、あなたの食欲をそそるために、私は最終結果をあげるだけです。 ただし、その実装方法については、記事の次の部分でのみ説明します。
-------------------------------- typelists.h -------------------------------- #pragma once struct EmptyList{}; template<typename Head, typename... Tail> struct TypeList { typedef Head head_t; typedef TypeList<Tail...> tail_t; }; template<typename Head> struct TypeList<Head, EmptyList> { typedef Head head_t; typedef EmptyList tail_t; }; typedef TypeList<int, double, bool, char, const char*, EmptyList> MyTypeList; ..... , ... -------------------------------- myclass.h -------------------------------- #pragma once class MyClass { public: template<typename T> void f(T x); }; -------------------------------- myclass.cpp -------------------------------- #include "templateclass.h" #include "typelist.h" #include <iostream> namespace { InstantiateMemFunc(MyClass, f, MyTypeList) // (*) } template<typename T> void MyClass::f(T x) { std::cout<< x << "\n"; } -------------------------------- main.cpp -------------------------------- #include <typelist.h> #include "myclass.h" int main() { MyClass tc; tc.f(3); tc.f(2.0); tc.f(true); tc.f('a'); tc.f("hello world"); return 0; }
問題があり、目標があることが明らかになったことを願っています。 通常、次のコードをmyclass.cppソースに貼り付けて、このプログラムを作成できるようにします。
template<> void MyClass::f<int>(int); template<> void MyClass::f<double>(double); template<> void MyClass::f<bool>(bool); template<> void MyClass::f<char>(char); template<> void MyClass::f<const char*>(const char*);
ここでは、誰もが彼が一番好きなもの、これまたはmyclass.cppの星付きの線を判断できます。
記事の最初の部分の残りは、ソースファイル内の型のリストのテンプレートをインスタンス化する決定に専念します。 最初の解決策が役立つのは、それが機能することだけです。 また、記事の2番目の部分では、myclass.cppファイル内の式
InstantiateMemberFunction
背後にあるもののベールを開きます。
それでは、ビジネスに取り掛かりましょう。型リストの関数テンプレートをインスタンス化する必要があります。 タスクを2つのサブタスクに分割します。
1)タイプのリストに対して何かをする方法と
2)特定のタイプのテンプレートをインスタンス化する方法。
最初のサブタスクから始めましょう。 ここでは、再帰でテクニックを盗むことを除いて、何も思い浮かびません。 さて、例えば、このように:
template<typename Types> struct SomethingDoer; // , - template<> struct SomethingDoer<EmptyList> // { static void doSomething(...) // - , . {} }; template<typename Head, typename... Tail> struct SomethingDoer<TypeList<Head, Tail...> > // . { static void doSomething() { ... - - Head .... (**) SomethingDoer<typename TypeList<Head, Tail...>::tail_t>::doSomething(); // } };
これで、タスクの目標が非常に明確になります。 2つのアスタリスクのある行では、特定の1つのタイプ(ヘッド)に必要な機能をインスタンス化する必要があります。
2番目のサブ問題に進みましょう。
質問:関数テンプレートをインスタンス化するにはどうすればよいですか? 5秒
時間切れです。 回答:明示的および暗黙的に。
明らかに、すでに説明したように、同行するには時間がかかりすぎます。 暗黙的にはどうですか?
質問:関数テンプレートを暗黙的にインスタンス化する2つの方法は何ですか? 10秒 さて、さらに10 10秒。 しかし、今は時間があります。
答えは:
最初の方法は、テンプレートパラメータを表示または直接指定できるように関数を呼び出すことです。
2番目の方法は、テンプレートのインスタンスのタイプを持つ変数を定義し、それを割り当てることです...ええと、どうすればもっと良くできますか...テンプレートのアドレス(はい、私は知っています、テンプレートにはアドレスがありませんが、異なる方法で呼び出す方法がわかりません)。
例は、単語のランダムなセットよりも優れています。
template <typename T> void f() {...} template <typename T> void g(T t) {...} struct S { template <typename T> void memFunc(){...} }; f<int>(); // g(58); // void (*gPtr)(int) = g; // void (S::*memFuncPtr)() = &S::memFunc<- , , int>; //
どの方法が私たちにとって良いかについて、私はあまりおろしませんが、長い間引きずられます。 私はすぐに言わなければならない-2番目の方がうまくいきます。
あなたは私に同意するので、2番目の方法でテンプレートをインスタンス化してみましょう。
template<typename Head, typename... Tail> struct SomethingDoer<TypeList<Head, Tail...> > { typedef void (MyClass::*MemFuncType)(Head); static void doSomething() { MemFuncType x = &MyClass::f; (void)(x); // , SomethingDoer<typename TypeList<Head, Tail...>::tail_t>::doSomething(); } };
タイプのリストからテンプレートをインスタンス化するメカニズムであるVoilaが作成されましたが、まだ有効になっていません。 上記のSomethingDoerクラスの定義と、doSomethingメソッドを、ソースファイルの名前のないネームスペース(この場合はmyclass.cpp)のどこかに書いたと想像してください。 MyClass :: f(T)テンプレートは、目的のタイプリスト用にインスタンス化されますか? 残念ながら、ありません。 しかし、上記のコードを作成した目的を実行する方法。 はい、とても簡単です。 呼び出す必要があります:
SomethingDoer<MyTypeList>::instantiate();
しかし、この素晴らしい行はどこにあるのでしょうか? 同じ名前のない名前空間に? 何も返しません。変数に割り当てることはできません。 さて、まとめてください:
struct Instantiator { Instantiator() { SomethingDoer<MyTypeList>::instantiate(); } };
使用しているコンパイラーはわかりませんが、gcc-4.8.1はこのコードをコンパイルする必要があります。 デバッグモードでは、ビルドが可能です。 しかし、それ以上ではありません。 本番モードではどうなりますか? ケースで使用されていないものはすべて犬のふすまに投げられます。 しかし、このケースでは最も重要なもの、つまり
doSomething
メソッドのローカル変数
x
と
Instantiator
クラスのコンストラクターは使用されません。 しかし、これは問題ではありません。 これら2つのことはまだ非常に重要であることをコンパイラーに納得させる必要があります。
すると、すべてが簡単です。 たとえば、変数volatileを宣言し、コンパイラーだけがそれを使ってなんとかすることができます。 そして、
Instantiator
コンストラクターを使用して、
Instantiator
型の変数を取り、宣言しましょう。
ソースコードは次のようになります。
#include "myclass.h" #include "typelist.h" #include <iostream> namespace { template<typename Types> struct SomethingDoer; template<> struct SomethingDoer<EmptyList> { static void doSomething(...) {} }; template<typename Head, typename... Tail> struct SomethingDoer<TypeList<Head, Tail...> > { typedef void (MyClass::*MemFuncType)(Head); static void doSomething() { volatile MemFuncType x = &MyClass::f; (void)(x); SomethingDoer<typename TypeList<Head, Tail...>::tail_t>::doSomething(); } }; template <typename TList> struct Instantiator { Instantiator() { SomethingDoer<TList>::doSomething(); } }; Instantiator<MyTypeList> a; // . } template<typename T> void MyClass::f(T x) { std::cout<< x << "\n"; }
それだけです、私たちはすでに最小限のタスクに対処しています。 テンプレートは、ヘッダーファイルではなくソースで定義され、タイプのリスト用にインスタンス化されます。 必要なタイプのリストに対するすべての変更は、インスタンスメカニズムに自動的に反映されます。タイプのリストが変更された場合、明示的なテンプレートインスタンスを手動で登録する必要はありません。 1つの小さな欠陥だけが目を引きます。このコード全体は完全なmonthさであり、1か月後にそれを開くと、同僚や数十人の「水電話」と「zhnei」が自分自身を抱えることになります。
具体化する前に、この解決策が何が悪いのか、すべての蓄積されたコードを1か所(カットの下)に集めて、中間結果を要約しましょう。
// myclass.h #pragma once class MyClass { public: template<typename T> void f(T x); }; // myclass.cpp #include "templateclass.h" #include "typelist.h" #include <iostream> namespace { template<typename Types> struct SomethingDoer; template<> struct SomethingDoer<EmptyList> { static void doSomething(...) {} }; template<typename Head, typename... Tail> struct SomethingDoer<TypeList<Head, Tail...> > { typedef void (MyClass::*MemFuncType)(Head); static void doSomething() { volatile MemFuncType x = &MyClass::f; (void)(x); SomethingDoer<typename TypeList<Head, Tail...>::tail_t>::doSomething(); } }; template <typename TList> struct Instantiator { Instantiator() { SomethingDoer<TList>::doSomething(); } }; Instantiator<MyTypeList> a; // . } template<typename T> void MyClass::f(T x) { std::cout<< x << "\n"; } // typelists.h #pragma once struct EmptyList{}; template<typename Head, typename... Tail> struct TypeList { typedef Head head_t; typedef TypeList<Tail...> tail_t; }; template<typename Head> struct TypeList<Head, EmptyList> { typedef Head head_t; typedef EmptyList tail_t; }; typedef TypeList<int, double, bool, char, const char*, EmptyList> MyTypeList; // main.cpp #include <typelist.h> #include "myclass.h" int main() { MyClass tc; tc.f(3); tc.f(2.0); tc.f(true); tc.f('a'); tc.f("hello world"); return 0; }
このソリューションの主な欠点は、そのさに加えて、プライベートであるということです。 1つの特定のクラスの1つの特定の関数のテンプレートをインスタンス化しました。 別のクラスでは、異なるソースで同じことをする必要があります。 また、テンプレートがいくつかある場合は、
doSomething
メソッドを少し拡張する必要がありますが、これはすべての悪の中でも最も小さいものです。 最大の欠点は、インスタンスメカニズムのコードとこのメカニズムの開始コードが密接に絡み合っているため、各ユーザーがこれがどのように行われるかを理解する必要があることです。 メカニズムがどれだけ大きくて混乱していても、メカニズムを隠しておくといいでしょう。 したがって、非表示にして、ユーザーが書くだけでよいようにします。
InstantiateMemFunc(MyClass, f, MyTypeList) // (*)
しかし、これについては第2部で説明します。