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

7

10

I am trying to use template meta-programming to determine the base class. Is there a way to get the base class automatically without explicitly specializing for each derived class?

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
}

for right now any automatic method would need to select the first declared base and would fail for private bases.

Lorsung answered 3/1, 2012 at 7:54 Comment(5)
Use std::is_base_of<B,D>Conte
@iammilind: That's only for testing if one class is the base of another, you have to know the base class to test against already.Sudra
What do you need it for? I don't think it's possible, but perhaps there is different approach to solve the actual problem.Mike
It's not possible and i seond taking a step back to the actual problem - explicit knowledge of the base class usually shouldn't be necessary.Coventry
By the way, binding a string literal to char* is deprecated. Use const char* instead.Sudra
K
5

My solutions are not really automatic, but the best I can think of.

Intrusive C++03 solution:

class B {};

class A : public B
{
public:
    typedef B Base;
};

Non-intrusive C++03 solution:

class B {};

class A : public B {};

template<class T>
struct TypeInfo;

template<>
struct TypeInfo<A>
{
    typedef B Base;
};
Kingcup answered 3/1, 2012 at 8:53 Comment(1)
that is pretty much exactly what I came up with too. I was hoping there was some hardcore template trick that I wasn't aware of that could extract it.. I'll probably use the intrusive form for classes with complete creation control and the non-intrusive form for classes that are semi-foreign -thanksLorsung
S
20

It's possible with C++11 and decltype. For that, we'll exploit that a pointer-to-member is not a pointer into the derived class when the member is inherited from a base class.

For example:

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

The type of &derived::f will be void (base::*)(), not void (derived::*)(). This was already true in C++03, but it was impossible to get the base class type without actually specifying it. With decltype, it's easy and only needs this little function:

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

Usage:

#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();
}

Output:

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

As the last example shows, you need to use a member that is actually an inherited member of the base class you're interested in.

There are more flaws, however: The member must also be unambiguously identify a base class member:

struct base2{ void f(){} };

struct not_deducible2 : base, base2{};

int main(){
  decltype(base_of(&not_deducible2::f)) x; // error: 'f' is ambiguous
}

That's the best you can get though, without compiler support.

Sudra answered 3/1, 2012 at 8:32 Comment(2)
Thanks Xeo,that is a very interesting partial solution and very similar to what I am looking for. I will look into it furtherLorsung
Can you extend your solution to also accept case, where 'base' contains const and non const member function 'f'?Trematode
D
5

I am not aware of any base-class-selecting template, and I'm not sure one exists or is even a good idea. There are many ways in which this breaks extensibility and goes against the spirit of inheritance. When bar publicly inherits foo, bar is a foo for all practical purposes, and client code shouldn't need to distinguish base class and derived class.

A public typedef in the base class often scratches the itches you might need to have scratched and is clearer:

class foo { public: typedef foo name_making_type; ... };

int main() {
    Foo::name_making_type a;
    Bar::name_making_type b;
}
Dropsonde answered 3/1, 2012 at 8:4 Comment(2)
Good one. I was about to write this.Conte
It is for a very light-weight c++ reflection system. The "client" code does general class serialization and messaging with the minimum possible deformation of and/or additions to the original class.Lorsung
K
5

My solutions are not really automatic, but the best I can think of.

Intrusive C++03 solution:

class B {};

class A : public B
{
public:
    typedef B Base;
};

Non-intrusive C++03 solution:

class B {};

class A : public B {};

template<class T>
struct TypeInfo;

template<>
struct TypeInfo<A>
{
    typedef B Base;
};
Kingcup answered 3/1, 2012 at 8:53 Comment(1)
that is pretty much exactly what I came up with too. I was hoping there was some hardcore template trick that I wasn't aware of that could extract it.. I'll probably use the intrusive form for classes with complete creation control and the non-intrusive form for classes that are semi-foreign -thanksLorsung
A
1

What's with the base class? Are you a .NET or Java programmer?

C++ supports multiple inheritance, and also does not have a global common base class. So a C++ type may have zero, one, or many base classes. Use of the definite article is therefore contraindicated.

Since the base class makes no sense, there's no way to find it.

Ayrshire answered 3/1, 2012 at 8:50 Comment(3)
There are strong efficiency reasons Java only allows one base class and I tend to restructure my c++ code for the same reasons - so it does make sense as a useful subsetLorsung
@VonRiphenburger: Then the question needs to state: "For a class with exactly one immediate base class, inherited publicly and non-virtually, is it possible to discover the base class type?" And it still isn't clear whether you're looking for the immediate base class, or another ancestor. And Java allowing only one base class has nothing to do with efficiency, and a lot to do with wanting language design to force coders down "the one true path to OOP". Ditto for lack of free functions.Ayrshire
I DID say for the FIRST declared base. It seemed obvious that immediate base was being sought, not a random ancestor, but that point could have been more clearly stated. Um, thanks for your input, ben.Lorsung
H
1

I am looking for a portable resolution for similar problems for months. But I don't find it yet.

G++ has __bases and __direct_bases. You can wrap them in a type list and then access any one of its elements, e.g. a std::tuple with std::tuple_element. See libstdc++'s <tr2/type_traits> for usage.

However, this is not portable. Clang++ currently has no such intrinsics.

Hoye answered 26/4, 2016 at 5:29 Comment(0)
W
1

With C++11, you can create a intrusive method to always have a base_t member, when your class only inherits from one parent:

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();
    }
};

With that class, you will always have a parent_t alias, to call the parent constructors as if it were the base constructors with a (probably) shorter name, and a base_t alias, to make your class non-aware of the base class type name if it's long or heavily templated.

The parent_t alias is protected to don't expose it to the public. If you don't want the base_t alias is public, you can always inherit labeled_base as protected or private, no need of changing the labeled_base class definition.

That base should have 0 runtime or space overhead since its methods are inline, do nothing, and has no own attributes.

Whallon answered 30/3, 2017 at 1:20 Comment(0)
F
-1

Recently when I reading Unreal Engine source code, I found a piece of code meet your requirement. Simplified code is below:

#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();
}

Using a macro below to wrap those code make it simple:

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

I got confused when first seeing these code. After I read @Xeo 's answer and @juanchopanza 's answer, I got the point. Here's the keypoint why it works:

  • The decltype expression is part of the member declaration, which does not have access to data members or member functions declared after it.

For example: In the declaration of class Derived1, when declaring Derived1::BaseType, Derived1::BaseType doesn't know the existence of Derived1::DerivedType. Because Derived1::BaseType is declared before Derived1::DerivedType. So the value of Derived1::BaseType is Base not Derived1.

Fatma answered 19/10, 2022 at 8:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.