Using Reflection in C++26 and a Toaster to Learn Reflection

How hard is it to put a Boeing 737 safely on the ground? I asked my toaster, and it said: extremely difficult.

Not something you would like to hear when the runway is already visible through the front windshield. Not much time left to learn the basics of avionics, cockpit instruments, procedures, and the rest.

Luckily, I used to write software for full-flight simulators — sophisticated machines used so future pilots don’t need to crash real planes while learning how to operate them.

All we need is reflection.

Okay, probably more. But from the whole codebase I worked with, reflection was the part I remember as the most difficult. The code was written by real C++ wizards and contained hundreds of lines of templates and macros, with the word META appearing almost every second line. All this magic just so we could access all the variables from the simulation engine.

If we can get reflection right, the rest of the task should be child’s play.

“Toaster, let’s talk about C++ and reflection.”

A quick crash course, and I was able to write down three lessons learned. What you see below is the version after multiple iterations, until I could finally prove to myself that my mind understood what was going on.

First lesson learned: reflection is just a mechanism for accessing the compiler’s knowledge about your code.

Second lesson learned: there is a cat ears* operator ^^ in C++. It returns a std::meta::info value that acts as our handle to the reflected stuff, and we can use a set of std::meta:: functions to get useful information from it. All of this happens at compile time.

Third lesson learned: the opposite direction of the cat ears operator is [: :]. I don’t have a name for it yet, but it looks quite toasterish.

“Toaster, am I missing anything on my list?”

Fourth lesson learned: reflection itself happens at compile time, but the code produced using it can operate on runtime objects. I thought I already understood this one, but the toaster insisted that I put it on the list anyway.

Closely related to the fourth lesson are annotations. Those are actually interesting because they are explicitly designed to attach information to declarations that reflection can later observe.

[[=cli_hidden{}]]
float internal_state;

Of course there was more, but the ground was getting closer, so there was no time for this. It was time to practice.

ctrl+T
tmux
claude

create a simple project (cmake) showing usage of reflections in cpp (^^operator). Class aircraft containing pitch, roll, yaw – float and landed – bool. All 4 should be exposed via CLI using reflection mechanism

Claude was “gesticulating” for more than 10 minutes. Luckily, our plane was a very slow one, so I could go make a coffee, take my dog out, and come back before it was done.

The generated code compiled. It worked. And it was perfect as a starting point for exploring the topic.

And that was enough — a small playground to mess with the code. What happens if I add this, remove that, or hit the whole thing with a hammer a few times? Just code-monkey playtime mode.

After the monkey finished playing, we were ready.


  template <typename T> constexpr auto members = std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()));

  // ...
    
  Aircraft ac;
  template for (constexpr auto m : members<Aircraft>) {
      if constexpr (std::meta::identifier_of(m) == "landed") {
          if constexpr (std::meta::type_of(m) == ^^bool) {
              ac.[:m:] = true;
          }
      }
  }

Touchdown.

* Finally! After all those years, we have something cool in our language. I was always jealous that Kotlin had its Elvis operator ?:

Author’s note: there was a time when I asked the Toaster for illustrations in the “Thinking Toasters” style and got nice, warm, happy drawings. Now I get neon pink fuchsia. I think the ghost of Mean Girl is here for good. See previous post.

Leave a comment