The Short and Long of C++ Type Introspection
I first wrote this blog post a year ago, but a misplaced sense of responsibility has called me to keep it updated. The first time I wrote this article I considered it a good resource for other DigiPen students who might be implementing a similar system. I was doing my part.
The first time I updated this post was October 19th of last year (2017) after some time working with a team that utilized an identical system to the one outlined in the article. I decided to update it because the optimism and faith I had in my first post was misplaced. I wanted to warn those who had read the previous post about the beasts I had encountered using my own guidelines. I was doing what was right.
Now with almost another year under my belt and full C++ type reflection being considered for the C++ standard I feel it is necessary to come back for a complete rewrite to tell you: I was wrong.
You see, C++ Type Introspection is a loosing battle. Until it makes its way into the standard, there will be no right way to hack it into your project. Just a list of bad ways. Poisons upon you’re entire project and especially its build systems. If you have come here seeking the optimistic guidance that once lived here instead you find I, the merchant of death, that shall at least inform you of toxin you shall soon swallow.
Before I let you do your shopping we first must get some terminology straight:
- Type Introspection: The ability to interact a class' members and methods at runtime
- Type Reflection: Type Introspection + the ability to add/remove members and methods at runtime
These are technical definitions which I will use throughout this article, but everyone just calls introspection reflection (even the standard). It makes sense for C++ because I don’t see the standard getting true Type Reflection ever. It would compromise performance. However, I’ll be careful because (DigiPen students take note): there are other languages than C++ and this distinction is important.
So back to C++. I’m familiar with two ways to create custom C++ type introspection both of which I’ll outline on this blog in later posts. For this blog I’m going to go over aspects that affect both methods; starting with what makes a good implementation:
A good implementation will:
- have minimal impact on build times
- have a clean interface for adding new managed types
- have readable intermediary code for reasonable debugging
- be as simple as possible while still while implementing everything that's necessary
So what is “necessary”? The simplest yet still useful Type Introspection system that is still useful can:
- be able to register both user defined types and basic/library types to be managed by the introspection system
- represent a compiled C++ class at runtime with an object
- get a list of class members
- be able to retrieve the values of those members
- be able to set the values of those members
- get a list of class methods
- be able to call those methods provided with a instance of the class
- get the size of the class
- construct the class into a byte buffer
(Makin good use of those lists)
So yea, that’s a lot just for the simplest implementation. This is the minimum in order to use the introspection system in order to write generic serialization code which was my original reason for implementing introspection.
However, some cool things you can do with an introspection system:
- automatically build editor UI for managed types
- automatically reveal managed types to a scripting language
So that isn’t a lot really, but overall the coolest thing about having introspection is being able to write scalable code. If you use introspection to write a serialization system any class managed by the introspection can be written and read from a file which is a huge win. That doesn’t seem that impressive when you consider C# has a standard library function for that, but C# is slower for other reasons so we’re stuck with C++.
From here I’ll direct you to the two different methods of implementation:
- Hiding breadcrumbs in managed classes and parsing them in clang's AST
- Macro and template wizardry