2xB
2xB

Reputation: 296

Check if class of one object is derived from class of another object in C++

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

Upvotes: 1

Views: 822

Answers (2)

Jeff Garrett
Jeff Garrett

Reputation: 7393

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).

Upvotes: 1

2xB
2xB

Reputation: 296

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

Upvotes: 0

Related Questions