基类的类型能否从模板类型中自动获得?

Can the type of a base class be obtained from a template type automatically?

本文关键字:类型 基类      更新时间:2023-10-16

我试图使用模板元编程来确定基类。是否有一种方法可以自动获得基类,而无需显式特化每个派生类?

class foo { public: char * Name() { return "foo"; }; };
class bar : public foo { public: char * Name() { return "bar"; }; };
template< typename T > struct ClassInfo { typedef T Base; };
template<> struct ClassInfo<bar> { typedef foo Base; };
int main()
{
  ClassInfo<foo>::Base A;
  ClassInfo<bar>::Base B;
  std::cout << A.Name();  //foo
  std::cout << B.Name();  //foo
}

目前,任何自动方法都需要选择第一个声明的基,并且对于私有基会失败。

这在c++ 11和decltype中是可能的。为此,我们将利用当成员从基类继承时,指向成员的指针不是指向派生类的指针。

例如:

struct base{
    void f(){}
};
struct derived : base{};

&derived::f的类型将是void (base::*)(),而不是void (derived::*)()。这在c++ 03中已经实现了,但是如果不实际指定基类类型,就不可能获得它。使用decltype,这很容易,只需要这个小函数:

// unimplemented to make sure it's only used
// in unevaluated contexts (sizeof, decltype, alignof)
template<class T, class U>
T base_of(U T::*);

用法:

#include <iostream>
// unimplemented to make sure it's only used
// in unevaluated contexts (sizeof, decltype, alignof)
template<class T, class R>
T base_of(R T::*);
struct base{
    void f(){}
    void name(){ std::cout << "base::name()n"; }
};
struct derived : base{
    void name(){ std::cout << "derived::name()n"; }
};
struct not_deducible : base{
    void f(){}
    void name(){ std::cout << "not_deducible::name()n"; }
};
int main(){
    decltype(base_of(&derived::f)) a;
    decltype(base_of(&base::f)) b;
    decltype(base_of(&not_deducible::f)) c;
    a.name();
    b.name();
    c.name();
}
输出:

base::name()
base::name()
not_deducible::name()

正如最后一个例子所示,您需要使用的成员实际上是您感兴趣的基类的继承成员。

然而,

有更多的缺陷:成员还必须明确地标识基类成员:

struct base2{ void f(){} };
struct not_deducible2 : base, base2{};
int main(){
  decltype(base_of(&not_deducible2::f)) x; // error: 'f' is ambiguous
}

在没有编译器支持的情况下,这是你能得到的最好结果。

我的解决方案不是自动的,而是我能想到的最好的。

侵入式c++ 03解决方案:

class B {};
class A : public B
{
public:
    typedef B Base;
};
非侵入式c++ 03解决方案:
class B {};
class A : public B {};
template<class T>
struct TypeInfo;
template<>
struct TypeInfo<A>
{
    typedef B Base;
};

我不知道有任何基类选择模板,我也不确定是否存在这样的模板,甚至不确定这是不是一个好主意。这在很多方面破坏了可扩展性,违背了继承的精神。当bar公开继承foo时,bar 实际上是一个 foo,客户端代码不需要区分基类和派生类。

基类中的公共类型定义通常可以解决您可能需要解决的问题,并且更清晰:

class foo { public: typedef foo name_making_type; ... };
int main() {
    Foo::name_making_type a;
    Bar::name_making_type b;
}

基类是怎么回事?你是。net或Java程序员吗?

c++支持多重继承,也没有全局通用基类。因此,c++类型可以有零个、一个或多个基类。因此,禁止使用定冠词。

由于基类没有意义,所以没有办法找到它

几个月来我一直在寻找类似问题的便携式解决方案。但是我还没有找到。

g++有__bases__direct_bases。你可以将它们包装在一个类型列表中,然后访问其中的任何一个元素,例如std::tuplestd::tuple_element。参考libstdc++的<tr2/type_traits>

然而,这是不可移植的。clang++目前还没有这样的特性

在c++ 11中,当你的类只继承一个父类时,你可以创建一个具有base_t成员的侵入式方法:

template<class base_type>
struct labeled_base : public base_type
{
    using base_t = base_type; // The original parent type
    using base::base; // Inherit constructors
protected:
    using base = labeled_base; // The real parent type
};
struct A { virtual void f() {} };
struct my_class : labeled_base<A>
{
    my_class() : parent_t(required_params) {}
    void f() override
    {
        // do_something_prefix();
        base_t::f();
        // do_something_postfix();
    }
};

对于这个类,您将始终拥有一个parent_t别名,以调用父类构造函数,就好像它是base构造函数一样,具有一个(可能)更短的名称,以及一个base_t别名,以使您的类不知道基类类型名称,如果它很长或大量模板化。

parent_t别名是受保护的,不会暴露给公众。如果您不希望base_t的别名是公共的,您可以将labeled_base继承为protectedprivate,而无需更改labeled_base的类定义。

该基类应该没有运行时或空间开销,因为它的方法是内联的,不做任何事情,也没有自己的属性。

最近当我阅读虚幻引擎源代码时,我发现一段代码符合您的要求。简化代码如下:

#include <iostream>
#include <type_traits>
template<typename T>
struct TGetBaseTypeHelper
{
    template<typename InternalType> static typename InternalType::DerivedType Test(const typename InternalType::DerivedType*);
    template<typename InternalType> static void Test(...);
    using Type = decltype(Test<T>(nullptr));
};
struct Base
{
    using DerivedType = Base;
    static void Print()
    {
        std::cout << "Base Logger" << std::endl;
    }
};
struct Derived1 : Base
{
    using BaseType = typename TGetBaseTypeHelper<Derived1>::Type;
    using DerivedType = Derived1;
    static void Print()
    {
        std::cout << "Derived1 Logger" << std::endl;
    }
};
struct Derived2 : Derived1
{
    using BaseType = typename TGetBaseTypeHelper<Derived2>::Type;
    using DerivedType = Derived2;
    static void Print()
    {
        std::cout << "Derived2 Logger" << std::endl;
    }
};

int main()
{
    Derived1::BaseType::Print();
    Derived2::BaseType::Print();
}

使用下面的宏来包装这些代码使其变得简单:

#define DECLARE_BASE(T) 
    public: 
    using BaseType = typename TGetBaseTypeHelper<T>::Type; 
    using DerivedType = T;

当我第一次看到这些代码时,我感到困惑。看了@Xeo和@juanchopanza的回答后,我明白了。下面是它工作的关键:

    decltype表达式是成员声明的一部分不能访问之后声明的数据成员或成员函数它。
例如

:在类Derived1的声明中,当声明Derived1::BaseType时,Derived1::BaseType不知道Derived1::DerivedType的存在。因为在Derived1::DerivedType之前声明了Derived1::BaseType。所以Derived1::BaseType的值是Base而不是Derived1