-.. _how-to-set-up-llvm-style-rtti:
-
======================================================
How to set up LLVM-style RTTI for your class hierarchy
======================================================
-.. sectionauthor:: Sean Silva <silvas@purdue.edu>
-
.. contents::
Background
double SideLength;
public:
Square(double S) : SideLength(S) {}
- double computeArea() /* override */;
+ double computeArea() override;
};
class Circle : public Shape {
double Radius;
public:
Circle(double R) : Radius(R) {}
- double computeArea() /* override */;
+ double computeArea() override;
};
The most basic working setup for LLVM-style RTTI requires the following
#include "llvm/Support/Casting.h"
-
#. In the base class, introduce an enum which discriminates all of the
- different classes in the hierarchy, and stash the enum value somewhere in
- the base class.
+ different concrete classes in the hierarchy, and stash the enum value
+ somewhere in the base class.
Here is the code after introducing this change:
public:
+ /// Discriminator for LLVM-style RTTI (dyn_cast<> et al.)
+ enum ShapeKind {
- + SquareKind,
- + CircleKind
+ + SK_Square,
+ + SK_Circle
+ };
+private:
+ const ShapeKind Kind;
You might wonder why the ``Kind`` enum doesn't have an entry for
``Shape``. The reason for this is that since ``Shape`` is abstract
(``computeArea() = 0;``), you will never actually have non-derived
- instances of exactly that class (only subclasses). See `Concrete Bases
+ instances of exactly that class (only subclasses). See `Concrete Bases
and Deeper Hierarchies`_ for information on how to deal with
non-abstract bases. It's worth mentioning here that unlike
``dynamic_cast<>``, LLVM-style RTTI can be used (and is often used) for
public:
/// Discriminator for LLVM-style RTTI (dyn_cast<> et al.)
enum ShapeKind {
- SquareKind,
- CircleKind
+ SK_Square,
+ SK_Circle
};
private:
const ShapeKind Kind;
double SideLength;
public:
- Square(double S) : SideLength(S) {}
- + Square(double S) : Shape(SquareKind), SideLength(S) {}
- double computeArea() /* override */;
+ + Square(double S) : Shape(SK_Square), SideLength(S) {}
+ double computeArea() override;
};
class Circle : public Shape {
double Radius;
public:
- Circle(double R) : Radius(R) {}
- + Circle(double R) : Shape(CircleKind), Radius(R) {}
- double computeArea() /* override */;
+ + Circle(double R) : Shape(SK_Circle), Radius(R) {}
+ double computeArea() override;
};
#. Finally, you need to inform LLVM's RTTI templates how to dynamically
public:
/// Discriminator for LLVM-style RTTI (dyn_cast<> et al.)
enum ShapeKind {
- SquareKind,
- CircleKind
+ SK_Square,
+ SK_Circle
};
private:
const ShapeKind Kind;
Shape(ShapeKind K) : Kind(K) {}
virtual double computeArea() = 0;
- +
- + static bool classof(const Shape *) { return true; }
};
class Square : public Shape {
double SideLength;
public:
- Square(double S) : Shape(SquareKind), SideLength(S) {}
- double computeArea() /* override */;
+ Square(double S) : Shape(SK_Square), SideLength(S) {}
+ double computeArea() override;
+
- + static bool classof(const Square *) { return true; }
+ static bool classof(const Shape *S) {
- + return S->getKind() == SquareKind;
+ + return S->getKind() == SK_Square;
+ }
};
class Circle : public Shape {
double Radius;
public:
- Circle(double R) : Shape(CircleKind), Radius(R) {}
- double computeArea() /* override */;
+ Circle(double R) : Shape(SK_Circle), Radius(R) {}
+ double computeArea() override;
+
- + static bool classof(const Circle *) { return true; }
+ static bool classof(const Shape *S) {
- + return S->getKind() == CircleKind;
+ + return S->getKind() == SK_Circle;
+ }
};
- Basically, the job of ``classof`` is to return ``true`` if its argument
- is of the enclosing class's type. As you can see, there are two general
- overloads of ``classof`` in use here.
+ The job of ``classof`` is to dynamically determine whether an object of
+ a base class is in fact of a particular derived class. In order to
+ downcast a type ``Base`` to a type ``Derived``, there needs to be a
+ ``classof`` in ``Derived`` which will accept an object of type ``Base``.
+
+ To be concrete, consider the following code:
+
+ .. code-block:: c++
+
+ Shape *S = ...;
+ if (isa<Circle>(S)) {
+ /* do something ... */
+ }
+
+ The code of the ``isa<>`` test in this code will eventually boil
+ down---after template instantiation and some other machinery---to a
+ check roughly like ``Circle::classof(S)``. For more information, see
+ :ref:`classof-contract`.
- #. The first, which just returns ``true``, means that if we know that the
- argument of the cast is of the enclosing type *at compile time*, then
- we don't need to bother to check anything since we already know that
- the type is convertible. This is an optimization for the case that we
- statically know the conversion is OK.
+ The argument to ``classof`` should always be an *ancestor* class because
+ the implementation has logic to allow and optimize away
+ upcasts/up-``isa<>``'s automatically. It is as though every class
+ ``Foo`` automatically has a ``classof`` like:
- #. The other overload takes a pointer to an object of the base of the
- class hierarchy: this is the "general case" of the cast. We need to
- check the ``Kind`` to dynamically decide if the argument is of (or
- derived from) the enclosing type.
+ .. code-block:: c++
- To be more precise, let ``classof`` be inside a class ``C``. Then the
- contract for ``classof`` is "return ``true`` if the argument is-a
- ``C``". As long as your implementation fulfills this contract, you can
- tweak and optimize it as much as you want.
+ class Foo {
+ [...]
+ template <class T>
+ static bool classof(const T *,
+ ::std::enable_if<
+ ::std::is_base_of<Foo, T>::value
+ >::type* = 0) { return true; }
+ [...]
+ };
+
+ Note that this is the reason that we did not need to introduce a
+ ``classof`` into ``Shape``: all relevant classes derive from ``Shape``,
+ and ``Shape`` itself is abstract (has no entry in the ``Kind`` enum),
+ so this notional inferred ``classof`` is all we need. See `Concrete
+ Bases and Deeper Hierarchies`_ for more information about how to extend
+ this example to more general hierarchies.
Although for this small example setting up LLVM-style RTTI seems like a lot
of "boilerplate", if your classes are doing anything interesting then this
For concrete bases (i.e. non-abstract interior nodes of the inheritance
tree), the ``Kind`` check inside ``classof`` needs to be a bit more
-complicated. Say that ``SpecialSquare`` and ``OtherSpecialSquare`` derive
+complicated. The situation differs from the example above in that
+
+* Since the class is concrete, it must itself have an entry in the ``Kind``
+ enum because it is possible to have objects with this class as a dynamic
+ type.
+
+* Since the class has children, the check inside ``classof`` must take them
+ into account.
+
+Say that ``SpecialSquare`` and ``OtherSpecialSquare`` derive
from ``Square``, and so ``ShapeKind`` becomes:
.. code-block:: c++
enum ShapeKind {
- SquareKind,
- + SpecialSquareKind,
- + OtherSpecialSquareKind,
- CircleKind
+ SK_Square,
+ + SK_SpecialSquare,
+ + SK_OtherSpecialSquare,
+ SK_Circle
}
Then in ``Square``, we would need to modify the ``classof`` like so:
.. code-block:: c++
- static bool classof(const Square *) { return true; }
- static bool classof(const Shape *S) {
- - return S->getKind() == SquareKind;
+ - return S->getKind() == SK_Square;
- }
+ static bool classof(const Shape *S) {
- + return S->getKind() >= SquareKind &&
- + S->getKind() <= OtherSpecialSquareKind;
+ + return S->getKind() >= SK_Square &&
+ + S->getKind() <= SK_OtherSpecialSquare;
+ }
The reason that we need to test a range like this instead of just equality
| OtherSpecialSquare
| Circle
+A Bug to be Aware Of
+--------------------
+
+The example just given opens the door to bugs where the ``classof``\s are
+not updated to match the ``Kind`` enum when adding (or removing) classes to
+(from) the hierarchy.
+
+Continuing the example above, suppose we add a ``SomewhatSpecialSquare`` as
+a subclass of ``Square``, and update the ``ShapeKind`` enum like so:
+
+.. code-block:: c++
+
+ enum ShapeKind {
+ SK_Square,
+ SK_SpecialSquare,
+ SK_OtherSpecialSquare,
+ + SK_SomewhatSpecialSquare,
+ SK_Circle
+ }
+
+Now, suppose that we forget to update ``Square::classof()``, so it still
+looks like:
+
+.. code-block:: c++
+
+ static bool classof(const Shape *S) {
+ // BUG: Returns false when S->getKind() == SK_SomewhatSpecialSquare,
+ // even though SomewhatSpecialSquare "is a" Square.
+ return S->getKind() >= SK_Square &&
+ S->getKind() <= SK_OtherSpecialSquare;
+ }
+
+As the comment indicates, this code contains a bug. A straightforward and
+non-clever way to avoid this is to introduce an explicit ``SK_LastSquare``
+entry in the enum when adding the first subclass(es). For example, we could
+rewrite the example at the beginning of `Concrete Bases and Deeper
+Hierarchies`_ as:
+
+.. code-block:: c++
+
+ enum ShapeKind {
+ SK_Square,
+ + SK_SpecialSquare,
+ + SK_OtherSpecialSquare,
+ + SK_LastSquare,
+ SK_Circle
+ }
+ ...
+ // Square::classof()
+ - static bool classof(const Shape *S) {
+ - return S->getKind() == SK_Square;
+ - }
+ + static bool classof(const Shape *S) {
+ + return S->getKind() >= SK_Square &&
+ + S->getKind() <= SK_LastSquare;
+ + }
+
+Then, adding new subclasses is easy:
+
+.. code-block:: c++
+
+ enum ShapeKind {
+ SK_Square,
+ SK_SpecialSquare,
+ SK_OtherSpecialSquare,
+ + SK_SomewhatSpecialSquare,
+ SK_LastSquare,
+ SK_Circle
+ }
+
+Notice that ``Square::classof`` does not need to be changed.
+
+.. _classof-contract:
+
+The Contract of ``classof``
+---------------------------
+
+To be more precise, let ``classof`` be inside a class ``C``. Then the
+contract for ``classof`` is "return ``true`` if the dynamic type of the
+argument is-a ``C``". As long as your implementation fulfills this
+contract, you can tweak and optimize it as much as you want.
+
+For example, LLVM-style RTTI can work fine in the presence of
+multiple-inheritance by defining an appropriate ``classof``.
+An example of this in practice is
+`Decl <http://clang.llvm.org/doxygen/classclang_1_1Decl.html>`_ vs.
+`DeclContext <http://clang.llvm.org/doxygen/classclang_1_1DeclContext.html>`_
+inside Clang.
+The ``Decl`` hierarchy is done very similarly to the example setup
+demonstrated in this tutorial.
+The key part is how to then incorporate ``DeclContext``: all that is needed
+is in ``bool DeclContext::classof(const Decl *)``, which asks the question
+"Given a ``Decl``, how can I determine if it is-a ``DeclContext``?".
+It answers this with a simple switch over the set of ``Decl`` "kinds", and
+returning true for ones that are known to be ``DeclContext``'s.
+
.. TODO::
Touch on some of the more advanced features, like ``isa_impl`` and
``simplify_type``. However, those two need reference documentation in
the form of doxygen comments as well. We need the doxygen so that we can
say "for full details, see http://llvm.org/doxygen/..."
+
+Rules of Thumb
+==============
+
+#. The ``Kind`` enum should have one entry per concrete class, ordered
+ according to a preorder traversal of the inheritance tree.
+#. The argument to ``classof`` should be a ``const Base *``, where ``Base``
+ is some ancestor in the inheritance hierarchy. The argument should
+ *never* be a derived class or the class itself: the template machinery
+ for ``isa<>`` already handles this case and optimizes it.
+#. For each class in the hierarchy that has no children, implement a
+ ``classof`` that checks only against its ``Kind``.
+#. For each class in the hierarchy that has children, implement a
+ ``classof`` that checks a range of the first child's ``Kind`` and the
+ last child's ``Kind``.