このソリューションには、s の比較が含まれtypeid
ます。TEImpl
自身の型を知っているので、渡されたものを自身のものと照合できtypeid
ます。
問題は、継承を追加するとこの手法が機能しないことです。そのため、テンプレートのメタプログラミングを使用して、型がtypedef super
定義されているかどうかを確認しています。その場合、親クラスを再帰的にチェックします。
struct TEBase
{
virtual ~TEBase() {}
virtual bool is_type(const type_info& ti) = 0;
};
template <typename T>
struct TEImpl : public TEBase
{
bool is_type(const type_info& ti) {
return is_type_impl<T>(ti);
}
template <typename Haystack>
static bool is_type_impl(const type_info& ti) {
return is_type_super<Haystack>(ti, nullptr);
}
template <typename Haystack>
static bool is_type_super(const type_info& ti, typename Haystack::super*) {
if(typeid( Haystack ) == ti) return true;
return is_type_impl<typename Haystack::super>(ti);
}
template <typename Haystack>
static bool is_type_super(const type_info& ti, ...) {
return typeid(Haystack) == ti;
}
};
template <typename T>
bool is_derived_from(TEBase* p)
{
return p->is_type(typeid( T ));
}
これを使用するには、次のBar
ように再定義する必要があります。
struct Bar : public Foo
{
typedef Foo super;
};
typedef super
これはかなり効率的ですが、継承が使用されている場合は常にターゲット クラスにが必要になるため、明らかに非侵入的ではありません。またtypedef super
、公開されている必要があります。これはtypedef super
、プライベート セクションにユーザーを配置することを推奨する慣行であると多くの人が考えていることに反します。
また、多重継承もまったく扱いません。
更新:この解決策をさらに進めて、一般的で非侵入的にすることができます。
一般化する
typedef super
慣用的であり、すでに多くのクラスで使用されているため、優れていますが、多重継承は許可されていません。そのためには、タプルなど、複数の型を格納できる型に置き換える必要があります。
Bar
を次のように書き換えた場合:
struct Bar : public Foo, public Baz
{
typedef tuple<Foo, Baz> supers;
};
次のコードを TEImpl に追加することで、この形式の宣言をサポートできます。
template <typename Haystack>
static bool is_type_impl(const type_info& ti) {
// Redefined to call is_type_supers instead of is_type_super
return is_type_supers<Haystack>(ti, nullptr);
}
template <typename Haystack>
static bool is_type_supers(const type_info& ti, typename Haystack::supers*) {
return IsTypeTuple<typename Haystack::supers, tuple_size<typename Haystack::supers>::value>::match(ti);
}
template <typename Haystack>
static bool is_type_supers(const type_info& ti, ...) {
return is_type_super<Haystack>(ti, nullptr);
}
template <typename Haystack, size_t N>
struct IsTypeTuple
{
static bool match(const type_info& ti) {
if(is_type_impl<typename tuple_element< N-1, Haystack >::type>( ti )) return true;
return IsTypeTuple<Haystack, N-1>::match(ti);
}
};
template <typename Haystack>
struct IsTypeTuple<Haystack, 0>
{
static bool match(const type_info& ti) { return false; }
};
邪魔にならないようにする
これで、効率的で一般的な解決策が得られましたが、依然として侵入的であるため、変更できないクラスはサポートされません。
これをサポートするには、クラスの外部からオブジェクトの継承を宣言する方法が必要です。の場合Foo
、次のようなことができます。
template <>
struct ClassHierarchy<Bar>
{
typedef tuple<Foo, Baz> supers;
};
そのスタイルをサポートするには、まず ClassHierarchy の特殊化されていない形式が必要です。これを次のように定義します。
template <typename T> struct ClassHierarchy { typedef bool undefined; };
の存在を使用してundefined
、クラスが特殊化されているかどうかを判断します。
次に、TEImpl にさらに関数を追加する必要があります。以前のコードのほとんどを引き続き再利用しますが、 からの型データの読み取りもサポートするようになりましたClassHierarchy
。
template <typename Haystack>
static bool is_type_impl(const type_info& ti) {
// Redefined to call is_type_external instead of is_type_supers.
return is_type_external<Haystack>(ti, nullptr);
}
template <typename Haystack>
static bool is_type_external(const type_info& ti, typename ClassHierarchy<Haystack>::undefined*) {
return is_type_supers<Haystack>(ti, nullptr);
}
template <typename Haystack>
static bool is_type_external(const type_info& ti, ...) {
return is_type_supers<ClassHierarchy< Haystack >>(ti, nullptr);
}
template <typename Haystack>
struct ActualType
{
typedef Haystack type;
};
template <typename Haystack>
struct ActualType<ClassHierarchy< Haystack >>
{
typedef Haystack type;
};
template <typename Haystack>
static bool is_type_super(const type_info& ti, ...) {
// Redefined to reference ActualType
return typeid(typename ActualType<Haystack>::type) == ti;
}
そして今、効率的で、一般的で、邪魔にならないソリューションがあります。
将来のソリューション
この解決策は基準を満たしていますが、クラス階層を明示的に文書化する必要があるのはまだ少し面倒です。コンパイラはすでにクラス階層についてすべて知っているので、この面倒な作業をしなければならないのは残念です。
この問題に対する提案された解決策は、N2965: Type traits and base classesで、これは GCC で実装されています。このペーパーでは、クラスを定義します。このクラスは、唯一の要素である が のようなタプルであることが保証されていることを除いてdirect_bases
、私たちのクラスとほとんど同じであり、クラスはコンパイラによって完全に生成されます。ClassHierarchy
type
supers
したがって、今のところ、これを機能させるために少しボイラープレートを作成する必要がありますが、N2965 が受け入れられれば、ボイラープレートを取り除き、TEImpl をはるかに短くすることができます。
Kerrek SB と Jan Herrmann に感謝します。この回答は、彼らのコメントから多くのインスピレーションを得ました。