该解决方案涉及比较typeid
s。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
无论何时使用继承,它都需要目标类中的 a 。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
,保证是一个元组, like supers
,并且该类完全由编译器生成。
所以现在我们必须编写一个小样板来让它工作,但如果 N2965 被接受,我们可以摆脱样板并使 TEImpl 更短。
特别感谢 Kerrek SB 和 Jan Herrmann。这个答案从他们的评论中得到了很多启发。