79409161

Date: 2025-02-03 14:53:26
Score: 0.5
Natty:
Report link

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.

Reasons:
  • Long answer (-1):
  • Has code block (-0.5):
  • Unregistered user (0.5):
  • Starts with a question (0.5): When
  • Low reputation (1):
Posted by: Netty Unreal