此解决方案涉及比较typeids。 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,即has been implemented in GCC。这篇论文定义了一个direct_bases类,和我们的ClassHierarchy类几乎一模一样,只是它唯一的元素type保证是一个元组,就像supers一样,而且这个类完全由编译器生成.
所以现在我们必须编写一个小样板来让它工作,但如果 N2965 被接受,我们可以摆脱样板并使 TEImpl 更短。
特别感谢 Kerrek SB 和 Jan Herrmann。这个答案从他们的 cmets 中得到了很多灵感。