4

私は典型的な型消去の設定をしています:

struct TEBase
{
    virtual ~TEBase() {}
    // ...
};

template <typename T>
struct TEImpl : TEBase
{
    // ...
};

ここで質問です。このような 2 番目のクラス階層が与えられた場合、

struct Foo { };
struct Bar : Foo { };

struct Unrelated { };

が与えられたTEBase * p場合、 の動的な型が*pの形式TEImpl<X>であるかどうかを判断することは可能XですFooか? 言い換えれば、私は機能が欲しい:

template <typename T> bool is_derived_from(TEBase * p);

そのような:

is_derived_from<Foo>(new TEImpl<Foo>) == true
is_derived_from<Foo>(new TEImpl<Bar>) == true

is_derived_from<Foo>(new TEImpl<Unrelated>) == false

特に、一般的で、邪魔にならず、効率的なソリューションを探しています。この問題に対する 2 つの解決策を見つけました (回答として以下に掲載) が、いずれも 3 つの基準すべてを解決していません。

4

4 に答える 4

2

このようなもの:

template <typename Type, typename UnaryPredicate>
void DoPred(UnaryPredicate pred)
{
    if (T * p = dynamic_cast<Derived<T> *>(this))
    { 
        return pred(p->type);
    }
    return false;
}

これは 100% 普遍的なものではありませんDoPred<int>。より普遍的なソリューションは、virtual std::type_info type() const { return typeid(...); }メンバー関数を階層に追加し、それを使用て型が一致するかどうかを判断します (標準の型消去イディオム)。ただし、どちらのアプローチも同じ種類の RTTI を使用します。


説明の後:

現時点では、これは解決できないと思います。あなたが持っているのはTEBaseサブオブジェクトだけです。TEImpl<Bar>の一部、または の一部である可能性がありますがTEImpl<Unrelated>、これらのタイプはどちらも に関連してTEImpl<Foo>いません。これがあなたが求めているものです。

あなたは本質的にからTEImpl<Bar> 派生TEImpl<Foo>したものを求めています。これを行うには、実際には allTEImpl<T>から継承するTEImpl<std::direct_bases<T>::type>...必要があります。これは C++11 では不可能ですが、TR2 では可能になります。GCC はすでにそれをサポートしています。実装例を次に示します。(あいまいなベースのために警告が発生します。これは、より多くの作業で回避できますが、それでも機能します。)

#include <tr2/type_traits>

struct TEBase { virtual ~TEBase() {} };

template <typename T> struct TEImpl;

template <typename TL> struct Derivator;

template <typename TL, bool EmptyTL>
struct DerivatorImpl;

template <typename TL>
struct DerivatorImpl<TL, true>
: TEBase
{ };

template <typename TL>
struct DerivatorImpl<TL, false>
: TEImpl<typename TL::first::type>
, Derivator<typename TL::rest::type>
{ };

template <typename TL>
struct Derivator
: DerivatorImpl<TL, TL::empty::value>
{ };

template <typename T>
struct TEImpl
: Derivator<typename std::tr2::direct_bases<T>::type>
{
};

template <typename T>
bool is(TEBase const * b)
{
  return nullptr != dynamic_cast<TEImpl<T> const *>(b);
}


struct Foo {};
struct Bar : Foo {};
struct Unrelated {};

#include <iostream>
#include <iomanip>

int main()
{
  TEImpl<int> x;
  TEImpl<Unrelated> y;
  TEImpl<Bar> z;
  TEImpl<Foo> c;

  std::cout << std::boolalpha << "int ?< Foo: " << is<Foo>(&x) << "\n";
  std::cout << std::boolalpha << "Unr ?< Foo: " << is<Foo>(&y) << "\n";
  std::cout << std::boolalpha << "Bar ?< Foo: " << is<Foo>(&z) << "\n";
  std::cout << std::boolalpha << "Foo ?< Foo: " << is<Foo>(&c) << "\n";
}
于 2013-05-31T08:35:51.083 に答える
0

この解決策には、例外を少し悪用することが含まれます。TEImpl型が単にそのデータをスローする場合、is_derived_from探している型をキャッチできます。

struct TEBase
{
        virtual ~TEBase() {}

        virtual void throw_data() = 0;
};

template <typename T>
struct TEImpl : public TEBase
{
        void throw_data() {
                throw &data;
        }

        T data;
};

template <typename T>
bool is_derived_from(TEBase* p)
{
        try {
                p->throw_data();
        } catch (T*) {
                return true;
        } catch (...) {
                // Do nothing
        }
        return false;
}

このソリューションはうまく機能します。あらゆる継承構造と完全に連携し、完全に非侵入的です。

唯一の問題は、まったく効率的でないことです。例外はこのように使用されることを意図したものではなく、このソリューションは他のソリューションよりも何千倍も遅いと思います。

于 2013-05-31T08:29:11.933 に答える
0

このソリューションには、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、私たちのクラスとほとんど同じであり、クラスはコンパイラによって完全に生成されます。ClassHierarchytypesupers

したがって、今のところ、これを機能させるために少しボイラープレートを作成する必要がありますが、N2965 が受け入れられれば、ボイラープレートを取り除き、TEImpl をはるかに短くすることができます。

Kerrek SB と Jan Herrmann に感謝します。この回答は、彼らのコメントから多くのインスピレーションを得ました。

于 2013-05-31T08:36:06.120 に答える