Programmiersprache C++26: Reflexion zur Kompilierungszeit

C++26 kommt und führt unter anderem die Kompilierungszeit‑Reflexion ein, die einige Aufgaben vereinfacht, ohne auf Makros zurückgreifen zu müssen.

vorlesen Druckansicht 10 Kommentare lesen
(C) Franziska Panter

(Bild: Franziska Panter)

Lesezeit: 4 Min.
Von
  • Andreas Fertig

Im heutigen Beitrag meines C++-Blogs möchte ich über C++26 und eine der wahrscheinlich wirkungsvollsten Funktionen schreiben, die dem Arbeitsentwurf hinzugefügt wurden. Auch wenn C++26 noch ein paar Wochen bis zur offiziellen Fertigstellung braucht, wissen wir seit dem WG21-Sommertreffen im Juni 2025, was in C++26 enthalten sein wird.

Die C++ Werkbank – Andreas Fertig
Portrait von Andreas Fertig

Andreas Fertig ist erfahrener C++-Trainer und Berater, der weltweit Präsenz- sowie Remote-Kurse anbietet. Er engagiert sich im C++-Standardisierungskomitee und spricht regelmäßig auf internationalen Konferenzen. Mit C++ Insights (https://cppinsights.io) hat er ein international anerkanntes Tool entwickelt, das C++-Programmierenden hilft, C++ noch besser zu verstehen.

Der neue Standard wird viele spannende Verbesserungen bringen, aber die wahrscheinlich größte Veränderung ist die Reflexion (Reflection) zur Kompilierungszeit! In Sofia hat das Standardisierungs-Komitee sieben Reflection-Papiere für C++26 angenommen:

  • P1306R5: Expansion statements
  • P2996R13: Reflection for C++26
  • P3096R12: Function parameter reflection in reflection for C++26
  • P3293R3: Splicing a base class subobject
  • P3394R4: Annotations for reflection
  • P3491R3: define_static_{string,object,array}
  • P3560R2: Error handling in reflection

Die verlinkten Beiträge bieten genügend theoretischen Lesestoff.

Die wichtigste Frage ist: Was kannst du mit dieser neuen Funktion machen? Einige haben bereits ihre Ideen veröffentlicht.

Steve Downey hat ein Beispiel, das eine JSON-Zeichenkette zur Kompilierungszeit analysiert und daraus C++-Objekte erstellt. Der direkte Link zum Compiler Explorer lautet godbolt.org/z/YsEK418K6.

Das zweite Beispiel stammt von Jason Turner und ermöglicht es, Bindungen zu anderen Sprachen zu generieren. Der direkte Link zum Compiler Explorer lautet godbolt.org/z/6Y17EG984.

Ich finde beide Beispiele prima, aber will auch ein eigenes zeigen. Das Problem, das ich jahrelang zu lösen versucht habe und das auch in verschiedenen Schulungen und sogar in meinem eigenen Buch Programming with C++20 – Concepts, Coroutines, Ranges, and more auftaucht. Ich musste die bittere Pille schlucken, einen nicht so tollen Code zu zeigen.

Ich rede von Enums und nicht davon, wie man ein Enum in einen String konvertiert und umgekehrt. Der Code dafür ist übrigens in den oben verlinkten Beiträgen zu finden.

Nein, ich hab mindestens noch ein anderes Problem mit Enums: Iteration. Wie oft wollte ich schon über ein enum iterieren. Es gibt Lösungen, die meist makrobasiert und mit vielen Regeln sind. Zum Beispiel nur aufeinanderfolgende Zahlen und ein letztes Mitglied namens Last oder MAX. Aber was ist, wenn es Lücken in einem enum gibt? Wie

enum class Color { Transparent, Red = 2, Green, Blue = 8, Yellow };

Genau, dann greift die Regel, dass nicht aufeinanderfolgende Nummerierungen nicht erlaubt sind.

Folgender Code zeigt einen Ansatz, der anderen Sprachen wie C# ähnelt, in denen das Iterieren der enum-Werte ohne weitere Umstände möglich ist:

// #A
template<typename E>
requires std::is_enum_v<E>
constexpr inline auto num_enumerators_of{
  std::meta::enumerators_of(^^E).size()};

// #B
template<typename E>
requires std::is_enum_v<E>
consteval auto get_enum_values()
{
  std::array<E, num_enumerators_of<E>> res;

  template for(size_t i{}; constexpr auto& e : std::define_static_array(
                             std::meta::enumerators_of(^^E)))
  {
    res[i++] = [:e:];
  }

  return res;
}

Ich habe in #A eine Hilfsvariable erstellt, einfach weil es fĂĽr sich schon hilfreich ist, die Anzahl der Werte in einem enum zu ermitteln.

Videos by heise

Die Implementierung für die Aufgabe selbst befindet sich dann in #B. Du kannst dir eine andere Implementierung ausdenken, die für größere Enums besser geeignet ist, aber diese hier ist schön kurz und bündig.

Die Utility-Funktion sieht in Aktion nicht besonders aus, und man merkt nicht, dass im Hintergrund eine Reflexion stattfindet:

for(const auto e : get_enum_values<Color>()) {
  std::print("{} ", std::to_underlying(e));
}

std::println();

Wie du siehst, gibt #B den stark typisierten Enum-Wert zurück. Deshalb ist std::to_underlying erforderlich, wenn der Wert mit std::print verwendet wird. Das ist eine Designentscheidung: Der Code bleibt so lange wie möglich stark typisiert.

Es gibt noch weitere DesignĂĽberlegungen, beispielsweise ob get_enum_values auch eine Variable sein sollte, da sie fĂĽr jeden Typ konstant ist.

An dieser Stelle werde ich nicht alle neuen Teile erklären, da ich nur zeigen möchte, was mit C++26 möglich ist.

Den vollständigen Code zum Experimentieren findest du im Compiler Explorer.

P.S.: Falls du dich fragst, ob die Implementierung von #B für ein leeres enum, welches ein std::array der Größe Null ergibt, korrekt ist, lautet die Antwort: Ja, der Code ist korrekt. Einer der Vorteile von std::array ist, dass es einen Sonderfall für den Fall der Größe Null gibt. Ein Array im C-Stil wäre nicht gültig.

(rme)