C ++がリフレクションをサポートしていないことは知っていますが、テンプレートメタプログラミングを使用して紙のリフレクションをサポートしましたが、これがどのように実現されるかを理解していませんでした。テンプレートメタプログラミングを使用してC++でこれを実現する方法について、詳細や例を教えてください。
2 に答える
Obj 型の Type に "foo" という名前の Type 型のパブリック データ メンバーがあるかどうかをコンパイル時にテストする構造体の例を次に示します。C++11 の機能を使用します。C++03 の機能を使用して行うこともできますが、このアプローチの方が優れていると思います。
まず、std::is_class を使用して Obj がクラスかどうかを確認します。クラスでない場合、データ メンバーを持つことができないため、テストは false を返します。これは、以下の部分的なテンプレートの特殊化によって実現されます。
SFINAE を使用して、オブジェクトにデータ メンバーが含まれているかどうかを検出します。「クラス Obj の Type 型のデータ メンバへのポインタ」型のテンプレート パラメータを持つ struct ヘルパーを宣言します。次に、静的関数テストの 2 つのオーバーロードされたバージョンを宣言します。1 つ目は、失敗したテストを示す型を返し、省略記号を介して任意のパラメーターを受け入れます。オーバーロードの解決では、省略記号の優先順位が最も低いことに注意してください。成功を示す型を返す 2 つ目は、テンプレート パラメーター &U::foo を持つヘルパー構造体へのポインターを受け入れます。ここで、U が Obj にバインドされた test の呼び出しが、testresult に nullptr および typedef を指定して呼び出された場合に返されるものを確認します。コンパイラは、省略記号が最後に試行されるため、最初に 2 番目のバージョンの test を試行します。If helper<&Obj::foo> Obj が Type 型のパブリック データ メンバーを持っている場合にのみ真となる正当な型であり、このオーバーロードが選択され、testresult は std::true_type になります。これが正当な型でない場合、オーバーロードは可能な候補 (SFINAE) のリストから除外されるため、任意のパラメーター型を受け入れる残りのバージョンの test が選択され、testresult は std::false_type になります。最後に、testresult の静的メンバー値が、テストが成功したかどうかを示す静的メンバー値に割り当てられます。
この手法の欠点の 1 つは、明示的にテストしているデータ メンバーの名前 (私の例では "foo") を知る必要があることです。そのため、さまざまな名前に対してそれを行うには、マクロを作成する必要があります。
同様のテストを記述して、型に特定の名前と型の静的データ メンバーがあるかどうか、特定の名前の内部型または typedef があるかどうか、次のコマンドで呼び出すことができる特定の名前のメンバー関数があるかどうかをテストできます。与えられたパラメーターの型などですが、それは今の私の時間の範囲を超えています.
template <typename Obj, typename Type, bool b = std::is_class<Obj>::value>
struct has_public_member_foo
{
template <typename Type Obj::*>
struct helper;
template <typename U>
static std::false_type test(...);
template <typename U>
static std::true_type test(helper<&U::foo> *);
typedef decltype(test<Obj>(nullptr)) testresult;
static const bool value = testresult::value;
};
template <typename Obj, typename Type>
struct has_public_member_foo<Obj, Type, false> : std::false_type { };
struct Foo
{
double foo;
};
struct Bar
{
int bar;
};
void stackoverflow()
{
static_assert(has_public_member_foo<Foo, double>::value == true, "oops");
static_assert(has_public_member_foo<Foo, int>::value == false, "oops");
static_assert(has_public_member_foo<Bar, int>::value == false, "oops");
static_assert(has_public_member_foo<double, int>::value == false, "oops");
}
コンパイル時に型の特定の特性を照会することができます。最も単純なケースは、おそらく組み込みsizeof
演算子です。MadScientistが投稿したように、特定のメンバーを調べることもできます。
ジェネリックプログラミングまたはテンプレートメタプログラミングを使用するフレームワーク内には、通常、クラスの概要に関するコントラクトがあります( 概念として形式化されています)。
たとえば、STLはresult_type
関数オブジェクトにメンバーtypedefを使用します。
boost:result_of
(後でstd::result_of
)このコントラクトを拡張して、パラメーターがジェネリックである(つまり、オーバーロードされたテンプレートまたはテンプレートを持つoperator()
)関数オブジェクトの結果タイプを計算するために、ネストされたクラステンプレートを許可します。次にboost:result_of
、コンパイル時のリフレクションを実行して、クライアントコードが関数ポインタ、STL関数オブジェクト、または「テンプレート関数オブジェクト」の結果タイプを均一に判別できるようにし、どちらの場合でも「正しく機能する」より一般的なコードを記述できるようにします。補足:この特定のケースでは、C ++ 11の方がうまくいく可能性があります。これは、自明ではなく、広範なコンポーネントに基づいているため、例として使用しました。
さらに、特定のタイプを登録するときに、コンパイル時に推定される(またはクライアントコードによって渡される)メタ情報を含むデータ構造を出力するクライアントコードを使用することもできます。フレームワークコードは、たとえば、
typeid
演算子を使用して型のランタイム表現を取得し、特定のコンストラクター、デストラクタ、およびメンバー関数のセット(一部はオプション)の呼び出しスタブを生成し、この情報をstd::map
キー付きstd::type_index
(またはstd::type_info
古いバージョンの言語用の手書きラッパー)。プログラムの後の時点で、この情報は、一時的な存続期間があり、いくつかの操作を実行し、整理する同じタイプのインスタンスをさらに作成するアルゴリズムを実行するために、あるオブジェクトのタイプ(のランタイム表現)を指定して見つけることができます。
両方の手法を組み合わせると非常に強力です。コンパイル時に、テンプレートから生成された多くのバリエーションに対して積極的なインライン化を使用してコードを生成し、プログラムの実行時に同様の方法でタイムクリティカルでない部分とインターフェイスできるためです。時間。