1

If I got objects a and b, how do I determine if the class of a inherits from the class of b?


Background: I got a C++ library for which I would like to write a Python bindings generator. That library provides a set of classes derived from a common base class. For the Python bindings, I need a list of functions for all classes. I got a list of methods through nm -D myLibrary.so, but that is missing methods of classes inherited e.g. in the style of

template<class WrappedClass>
class Wrapper: public WrappedClass {
  public:
    // ...
};

typedef Wrapper<ClassA> ClassB;

. So I got all functions of classes like ClassA and would just like to know to which ClassB they belong.

I can get a list of names of all available classes from the library on runtime, and can get objects with those types via a factory function that accepts class names and gives objects of that type. So the last piece is to dynamically determine which classes like ClassB belong to which classes like ClassA. Therefore the question.

PS: I could in principle write a C++ code generator that generates test code that is then compiled against the library to determine which classes inherit from which others. That would come to the high cost of not being able to compile my code along with the library but requiring a second compilation. Since I have nearly everything apart from this problem here to circumvent that, I hope very much that there is a different solution.


PPS: I was told to use decltype in the comments. That does not work:

#include <iostream>
#include <type_traits>

struct A {};
struct B : A {};

int main() {
    A* a = new A();
    A* b = new B();
  std::cout << std::boolalpha;
  std::cout << "A, B: " << std::is_base_of<A,B>::value << std::endl;
  std::cout << "a, b: " << std::is_base_of<decltype(a),decltype(b)>::value << std::endl;
  std::cout << "*a, *b: " << std::is_base_of<decltype(*a),decltype(*b)>::value << std::endl;
  std::cout << "a., B: " << std::is_base_of<decltype(a),B>::value << std::endl;
  std::cout << "*a., B: " << std::is_base_of<decltype(*a),B>::value << std::endl;
  return 0;
}
}

yields

A, B: true
a, b: false
*a, *b: false
a., B: false
*a., B: false

PPPS: One answer suggested to use

std::is_base_of_v<std::decay_t<decltype(*a)>, std::decay_t<decltype(*b)>>

. I can't get it to work:

#include <iostream>
#include <type_traits>

struct A {};
struct B : A {};

int main() {
    A* a = new A();
    A* b = new B();
  std::cout << std::boolalpha;
  std::cout << "A, B: " << std::is_base_of<A,B>::value << std::endl;
  std::cout << "B, A: " << std::is_base_of<B,A>::value << std::endl;
  std::cout << "*a, *b: " << std::is_base_of<std::decay_t<decltype(*a)>, std::decay_t<decltype(*b)>>::value << std::endl;
  std::cout << "*b, *a: " << std::is_base_of<std::decay_t<decltype(*b)>, std::decay_t<decltype(*a)>>::value << std::endl;
  return 0;
}

yields:

A, B: true
B, A: false
*a, *b: true
*b, *a: true
2xB
  • 296
  • 1
  • 11
  • Are you looking for this: https://en.cppreference.com/w/cpp/types/is_base_of – Jerry Jeremiah Dec 04 '19 at 01:06
  • @JerryJeremiah In my understanding, `std::is_base_of` requires two classes to be passed, I've only got two objects. – 2xB Dec 04 '19 at 01:20
  • @2xB "*I can get a list of names of all available classes from the library on runtime*" can you? Would you show how you do it? This isn't possible using standard library directly, but if you're able to enumerate types, then it should be possible to cook something up. – luk32 Dec 04 '19 at 01:21
  • @2xB You can use [`decltype`](https://en.cppreference.com/w/cpp/language/decltype). – songyuanyao Dec 04 '19 at 01:22
  • I think it should be possible with a combo of `typeid`, `decltype` and `is_base_of`. One could iterate over all pair's and test them, but it's a rough idea. – luk32 Dec 04 '19 at 01:25
  • `static_assert(std::is_base_of_v,"d1 not derived from B1");` – Jerry Jeremiah Dec 04 '19 at 01:49
  • @luk32 The library provides this via a registry pattern. I did not do any magic there. – 2xB Dec 04 '19 at 12:53
  • @songyuanyao @luk32 @Jerry Jeremiah I added a PPS to show that `decltype` does not work (at least how I know it). If I did something wrong in that example, I would be happy to know about it. – 2xB Dec 04 '19 at 12:55
  • `decltype` is a compile-time operator, it cannot retrieve runtime-only information such as the dynamic type of a type-erased object. I don't think you can check for inheritance through RTTI. – Quentin Dec 04 '19 at 13:01
  • What do you mean by “got objects `a` and `b`”? Is this a runtime check? – Davis Herring Dec 04 '19 at 14:06
  • @DavisHerring Yes. On runtime, I can create these objects from a factory method that I provide with the name of the class. I get those names also at runtime from a called method. Everything I know about these objects is the name of the common base class, but not if one inherits from another. – 2xB Dec 04 '19 at 15:02
  • For the decltype problem, you need to `std::decay` the types to remove the references and const. `std::is_base_of, std::decay_t>::value`. But this uses compile-time types of `a` and `b`. C++ does not provide run-time class hierarchy information. You'll have to use some other mechanism to determine that. Hopefully the factory has some clues. If `A` is fixed, you can cheat and throw `b`, and then do `catch (A const&)` and see if it catches. – Raymond Chen Dec 04 '19 at 15:34
  • @RaymondChen: You don’t have to remove cv-qualifiers, but yes on the references. – Davis Herring Dec 05 '19 at 01:24

2 Answers2

1

If I got objects a and b, how do I determine if the class of a inherits from the class of b?

Answering the question asked, std::is_base_of:

std::is_base_of_v<std::decay_t<decltype(*a)>, std::decay_t<decltype(*b)>>

In your example, a and b were not actually objects, but pointers to objects, so you must dereference to get a reference to an object, take decltype to get the type, and then remove reference and const qualification using std::decay_t.

In C++, objects have both a static type and a dynamic type. std::is_base_of applies to the static type. For the dynamic type, dynamic_cast will tell you if the types are related, but there is no standard query which only answers if the dynamic type of one is the base of the dynamic type of the other. For wrapping in another language, the static type seems like the more interesting type for the purpose of which member functions one can call.

However, your approach to your real problem is flawed. Using nm to determine member function names of classes tells you nothing about the accessibility of those name, or how overloads are resolved. And std::is_base_of will answer the question asked, but it will return true if the base is inaccessible or ambiguous, in which case the member functions of the base don't directly apply to the derived.

Assuming you don't want to maintain your bindings by hand, the best way is to find a tool which can generate them (e.g. swig) or write such a tool (e.g. using python clang bindings).

Jeff Garrett
  • 5,863
  • 1
  • 13
  • 12
  • Since your answer only works for static types, my issue is not solved for that. I added another comment (a PPPS) to the original question to demonstrate the issue. – 2xB Dec 04 '19 at 22:05
  • This is true. I did answer that it's not portably possible with dynamic types. However, (a) I think static types is probably what you want when wrapping. If you have a wrapped A in some other language, it would be weird to be able to call B's methods on it, even if the dynamic type is B. In python, one creates something as a B, and use it as a B, and any wrapped function which expects an A, would be using it as an A anyways. And (b) I think regardless of the static/dynamic type differences, this is not a robust approach for wrapping for the reasons mentioned. – Jeff Garrett Dec 04 '19 at 22:18
  • Oh, sorry, I did not want to ask anything about wrapping. I wanted to determine which class is inherited from which other class, so I know which class has which functions inherited from other classes, that's all. The twist was that I only get the names of the classes at runtime of which I want to know inheritance, and that I have a method that generates objects of these classes at runtime if I provide it with their names. – 2xB Dec 07 '19 at 14:44
  • But the python clang bindings sound really interesting, I will definitely look into them! – 2xB Dec 07 '19 at 14:55
0

A solution I found is the following use of the Itanium ABI. It is mostly taken from https://stackoverflow.com/a/11675891/8575607 . Note that this does not work if the parent class does not have a virtual member (although I have no idea why).

So this:

#include <cassert>
#include <typeinfo>
#include <cxxabi.h>
#include <iostream>

bool is_ancestor(const std::type_info& a, const std::type_info& b);

namespace {
  bool walk_tree(const __cxxabiv1::__si_class_type_info *si, const std::type_info& a) {
    return si->__base_type == &a ? true : is_ancestor(a, *si->__base_type);
  }

  bool walk_tree(const __cxxabiv1::__vmi_class_type_info *mi, const std::type_info& a) {
    for (unsigned int i = 0; i < mi->__base_count; ++i) {
      if (is_ancestor(a, *mi->__base_info[i].__base_type))
        return true;
    }
    return false;
  }
}

bool is_ancestor(const std::type_info& a, const std::type_info& b) {
  if (a==b)
    return true;
  const __cxxabiv1::__si_class_type_info *si = dynamic_cast<const __cxxabiv1::__si_class_type_info*>(&b);
  if (si)
    return walk_tree(si, a);
  const __cxxabiv1::__vmi_class_type_info *mi = dynamic_cast<const __cxxabiv1::__vmi_class_type_info*>(&b);
  if (mi)
    return walk_tree(mi, a);
  return false;
}

class foo {virtual void f1(){}};
class bar : public foo {virtual void f2(){}};
class abc : public bar {virtual void f3(){}};

int main() {
  foo* myfoo = new foo();
  foo* mybar = new bar();
  foo* myabc = new abc();
  std::cout << std::boolalpha;
  std::cout << typeid(*mybar).name() << " " << typeid(*myfoo).name() << "\n";
  std::cout << is_ancestor(typeid(*mybar), typeid(*myfoo)) << "\n";
  std::cout << is_ancestor(typeid(*myfoo), typeid(*mybar)) << "\n";
  std::cout << is_ancestor(typeid(*myfoo), typeid(*myabc)) << "\n";
}

produces this output as expected:

3bar 3foo
false
true
true
2xB
  • 296
  • 1
  • 11
  • 1
    This won't work if your object doesn't have a vtable. vtables are typically only emitted for objects with virtual members (As they are only needed for resolving virtual calls). – gan_ Dec 04 '19 at 16:42