Using isa & co with custom class hierarchy

Hi!

I hope I'm not too OT with that question, but Clang makes extensive use
of this mechanism so maybe somebody can help me.

LLVM provides isa<>, cast<> and so on. Recently I tried to make use of
this for my own class hierarchy, but failed.

From the documentation and examples in Clang's code I figured that the

only thing to do was add a static method 'classof' to any involved class
with a parameter that will match any parent class. Then inside this
method do any checks necessary and return 'true' if the current instance
is indeed of the type given.

But when I do this I get 'true' for any 'isa<>' I try!?

Attached is a small example that outputs the following:

$ ./a.out
isa<A>(a) = 1
isa<B>(a) = 1
isa<B>(b) = 1
isa<A>(b) = 1

What I would expect instead is the second line to yield false/0, since a
is an instance of class A which "is" no B.

Can somebody please give me a hint on what I'm doing wrong?

Regards, Jan.

isa.cpp (574 Bytes)

Hi Jan,

I think ‘classof’ should be virtual for isa<> and friends to work. In addition you can provide a static ‘classof’ for the same class that will speed up the cases like isa(a) and isa(b).

Regards,
Victor

Sorry, ‘classof’ shouldn’t be virtual itself of course.
The problem is that B::classof(const A *) always return true in your implementation, so llvm::isa(a)returns true as well.
To make it work as you expect you should return true only if an argument to B::classof(const A *)is a pointer to class B or its subclass.
This can be done e.g. using a type field or a virtual function returning type identifier or dynamic_cast though if you use the latter you don’t need llvm::isa. You can have a look at how classof is implemented in clang’s AST.

Victor

Hi Victor!

Thanks for your answer.

Victor Zverovich meinte am 25.08.2010 18:33:

The problem is that B::classof(const A *) always return true in your
implementation, so llvm::isa<B>(a)returns true as well.

Yeah, but that one is intended. Every instance of B "is a" instance of A
due to inheritance. I just modeled this in the 'classof' method.

But even leaving this aside: There is no 'A::classof(const B *)' yet
'isa<B>(a)' returns 'true'. I added this one specifically to make sure I
did not mix up something with which class must define which as the
parameter to 'classof'.

You can have a look at how classof is implemented in clang's AST.

I actually did. First the short introduction LLVM gives [1] states

To add support for these templates, you simply need to add classof
static methods to the class you are interested casting to.

That's how I came up with the scheme at all. Then for example
'CXXRecordDecl' [2] define the following (besides others):

static bool classof(const CXXRecordDecl *D) { return true; }
static bool classof(const ClassTemplateSpecializationDecl *D) {
  return true;
}

I take this to mean that every 'CXXRecordDecl' is a 'CXXRecordDecl' -
well obviously - and every 'ClassTemplateSpecializationDecl' is a
'CXXRecordDecl'.

Hmm, looks I did mix up the directions, but my example should
nevertheless also work only one way - every 'A' being a 'B' but not vice
versa. So 'isa<A>(b)' should report false/0.

[1] LLVM Programmer’s Manual — LLVM 18.0.0git documentation
[2] clang: include/clang/AST/DeclCXX.h Source File

Regards, Jan.

Hi Victor!

Thanks for your answer.

Victor Zverovich meinte am 25.08.2010 18:33:

The problem is that B::classof(const A *) always return true in your
implementation, so llvm::isa<B>(a)returns true as well.

Yeah, but that one is intended. Every instance of B "is a" instance of A
due to inheritance. I just modeled this in the 'classof' method.

Here's how isa is implemented (leaving aside all the unwrapping magic):

template <typename Test, typename Arg>
bool isa(Arg *Ptr) {
  return Test::classof(Ptr);
}

For a class Class, Class::classof is supposed to check if Class is the dynamic type (or a superclass thereof) of the argument.

So A::classof(A*) tests whether A is a valid cast target for the given object, which is trivially true.
B::classof(A*) tests whether B is a valid cast target for the given object. This is NOT trivially true, since it's only true for objects whose dynamic type is actually B, but the A* could point to an A. This is where the real work needs to be done. However, you return true here, which is why isa<B>(a) returns true. Remember, isa<B>(a) == B::classof(a) == true in your implementation.
B::classof(B*), finally, is simply an optimization. When the type of the object is statically known to be at least a B, there's no need to perform a runtime check.

Then for example
'CXXRecordDecl' [2] define the following (besides others):

Those others are what's important. In particular, look at the overload that takes a Decl*.
In fact I think that the ClassTemplateSpecializationDecl overload here is completely superfluous. I have no idea why it's there.

So 'isa<A>(b)' should report false/0.

You don't have any classof function that can return false, ever. Therefore, all isa tests will always return true. (A missing classof is a compile error, not a result of false.)

Sebastian