jack
jack

Reputation: 576

C++ Reflection Guide/Tutorials

I am finding it difficult to find anyway to implement Reflection in C++. I have only seen examples from AGM::LibReflection and Game Programming Gems 5. Does anyone know of a tutorial or decent code sample for how to do this?

So far I know of Boost/QT's built in system, but I am not looking to use theirs (please do not hamper on this, I find it bloated and I want to roll my own, do not derail the topic).

Upvotes: -3

Views: 3144

Answers (3)

Netty Unreal
Netty Unreal

Reputation: 1

When talking about reflection, the 2 mistakes that are most common...

  1. They want a library or compiler feature that does it all for them. Not everything is a plugin.
  2. They want to write
for (const auto& mbr : my_struct)
{
   // but what is the type of mbr now, it changes for every member
   // you cannot "loop" over things of different types.
}

But... While most programmers find 'for loop's a comfortable and familiar way of writing code it is in-fact a bit of an anti-pattern in modern C++. You should prefer algorithms and "visitation". Once you learn to give up on iteration, and prefer visitation (passing functions to algorithms), you find that the pattern I describe below is quite usable.

So what is the easy way... Given just three techniques you can roll your own reflection system in C++17 onwards in a hundred lines of code or so.

  1. You can easily "visit" every member of a tuple in a few line in C++. From - https://en.cppreference.com/w/cpp/utility/apply
template<typename... Ts>
std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> const& theTuple)
{
    std::apply
    (
        [&os](Ts const&... tupleArgs)
        {
            os << '[';
            std::size_t n{0};
            ((os << tupleArgs << (++n != sizeof...(Ts) ? ", " : "")), ...);
            os << ']';
        }, theTuple
    );
    return os;
}

Understand this code before reading on...

What you need a system that makes tuples from structures. Boost-PFR or Boost-Fusion are good at this, if you want a quick-start to experiment on.

  1. The best way to access a member of a structure is using a pointer-to-member. See "Pointers to data members" at https://en.cppreference.com/w/cpp/language/pointer. The syntax is obscure, but this is a pre-C++11 feature and is a stable feature of C++.

  2. You can make a static-member function that constructs a tuple-type for your structure. For example, the code below makes a tuple of member pointers for "Point", pointers to the "offset" of the members x & y. The member-pointers can be determined at compile-time, so this comes with a mostly zero-cost overhead. member-pointers also retain the type of the object they point to and are type-safe. (Every compiler I have used will not actually generate a tuple, just generate the code produced, making this a zero-overhead technique... I can't promise this but it normally is) Example struct...

struct Point
{
    int x{ 0 };
    int y{ 0 };

    static consteval auto get_members() {
        return std::make_tuple(
            &Point::x,
            &Point::y
        );
    }
};

You can now wrap all the nastiness up in simple wrapper functions. For example.

// usage visit(my_point, [](const auto& mbr) { std::cout << mbr; });
// my_point is an object of the type point which has a get_members function.
template <class RS, class Fn>
void visit(RS& obj, Fn&& fn)
{
    const auto mbrs = RS::get_members();

    const auto call_fn = [&](const auto&...mbr)
    {
        (fn(obj.*mbr.pointer), ...);
    };
    std::apply(call_fn, mbrs);
};

To use all you have to do is make a "get_members" function for every class/structure you wish to use reflection on.

I like to extend this pattern to add field names and to allow recursive visitation (when the visit function sees another structure that has a "get_members" function it visits each member of that too). C++20 also allows you to make a "concept" of visitable_object, which gives better errors, when you make a mistake. It is NOT much code and while it requires you to learn some obscure features of C++, it is in fact easier than adding meta-compilers for your code.

Upvotes: -1

Ira Baxter
Ira Baxter

Reputation: 95324

Reflection is rotten way to inspect properties of a program. You can only "reflect" what the guy designing the compiler wired into the reflection machinery. That's usually not a lot (what reflection system do you know that will let you peer inside an expression?) and it depends on the language. And for something like C++, where you are trying to add reflection on top of the language essentially as set of APIs, you are going to be extremely limited, or you'll have to code in truly stilted style that lets you in effect declare the reflection data as standard data structures.

You could instead use a program transformation engine (PTS). Such an engine manipulates the complete program representation, and can thus technically answer any question which is answerable. In particular, it can in principle answer all the reflection questions you can imagine, because it acts as a substitute for the compiler, and can see everything the compiler sees. (In fact, it can see more than the compiler sees; the compiler is limited to one compilation unit at a time, and a good PTS can see an arbitrarily big set of compilation units concurrently, and can thus answer questions about the set as a whole).

Our DMS Software Reengineering Toolkit can parse full (and many dialects of) C++, builts ASTs and accurate symbol tables. Using that, you can implement any static computable reflection, and then use that to produce analysis results or modify the ASTs directly.

DMS is language agnostic; it can do this for a large variety of languages.

Regarding actually using DMS to do "reflection": OP wanted to know how one might implement property getters and setters. With a PTS like DMS, you parse the source code of the class of interest, and then walk the AST for the code. For each data declaration inside the class, you literally manufacture a getter for that data, by building up the AST that represents the getter code; for tools like DMS, you can do this by composing patterns of C++ source code that are interpreted to represent the corresponding AST fragments, complete with placeholders you can fill in with other ASTs. Minor transformations can then modify the original AST to include the generated getters/setters; this produces an AST that contains the original code and the generated getters/setters. A final step is to regenerat code from the AST, which DMS does by using AST-to-source prettyprinters that are part of the "domain definition" (parser, prettyprinter, name resolver) that make up the language (e.g., C++) front end.

Upvotes: 1

Tony Delroy
Tony Delroy

Reputation: 106066

YMMV, but there's GCCXML (www.gccxml.org/) and OpenC++ (http://opencxx.sourceforge.net/) if you're looking for an external tool. Mark-up libraries abound....

Upvotes: 0

Related Questions