Reflection in C++26: Metafunktionen für Enums und Klassen​

Der Blogbeitrag setzt die Reise durch Reflection in C++26 fort und beschäftigt sich mit Enums und Klassen.​

In Pocket speichern vorlesen Druckansicht
Holzwürfel Buchstaben C++

(Bild: SerbioVas/Shutterstock)

Lesezeit: 4 Min.
Von
  • Rainer Grimm
Inhaltsverzeichnis

Nach der Einführung zu Reflexion in C++26 und einer grundlegenden Einführung der Metafunktionen, geht es diesmal um Enums und Klassen.

Modernes C++ – Rainer Grimm

Rainer Grimm ist seit vielen Jahren als Softwarearchitekt, Team- und Schulungsleiter tätig. Er schreibt gerne Artikel zu den Programmiersprachen C++, Python und Haskell, spricht aber auch gerne und häufig auf Fachkonferenzen. Auf seinem Blog Modernes C++ beschäftigt er sich intensiv mit seiner Leidenschaft C++.

Die Namen der Metafunktionen für den Zugriff auf die Elemente einer Enum oder einer Klasse sind oft identisch.

Das folgende Programm basiert auf einem Programm von Daveed Vandevoorde. Daveed ist einer der Väter der Reflection in C++ und hat dieses Beispiel in seiner Präsentation „Reflections on C++ Reflection“ verwendet.

Das Programm durchläuft eine Enum und zeigt den Namen und den Wert für jeden Enumerator an.

// daveed.cpp

#include <array>
#include <experimental/meta>
#include <iostream>

template<typename E> 
struct enum_item { 
  std::string_view name; 
  E value;
};

template<typename E> 
consteval auto get_enum_data() {
  std::array<enum_item<E>, std::meta::enumerators_of(^E).size()> result;
  int k = 0;
  for (auto mem: std::meta::enumerators_of(^E))
    result[k++] = enum_item<E>{ std::meta::identifier_of(mem), std::meta::extract<E>(mem) };
  return result;
}

enum MyEnum { 
  x, 
  y, 
  e = -1, 
  z = 99 
};

int main() {

  std::cout << '\n';

  std::cout << "members of " << std::meta::identifier_of(^MyEnum) << '\n';
  for (auto x: get_enum_data<MyEnum>()) {
    std::cout << "  " << x.name << " = " << (long)x.value << '\n';
  }

  std::cout << '\n';

}

Das bereitgestellte Code-Snippet veranschaulicht die Verwendung experimenteller Metaprogrammierungs-Features in C++ zur Überprüfung und Bearbeitung von Aufzählungstypen zur Compile-Zeit. Der Code beginnt mit dem Einfügen der erforderlichen Header: <array> für die Array-Unterstützung, <experimental/meta> für Metaprogrammierungs-Dienstprogramme und <iostream> für Eingabe-Ausgabe-Operationen.

Das enum_item struct Template ist so definiert, dass es Informationen über ein Aufzählungselement enthält. Es enthält zwei Elemente: name ist ein std::string_view, das den Namen des Aufzählungselements darstellt, und value, das den Wert des Aufzählungselements vom Datentyp E enthält.

Das get_enum_data function Template ist als consteval markiert, was bedeutet, dass es zur Compile-Zeit ausgewertet wird. Diese Funktion generiert ein Array von enum_item-Strukturen für einen bestimmten Aufzählungstyp E. Sie verwendet die std::meta::enumerators_of-Funktion, um die Aufzählungselemente des Aufzählungstyps abzurufen, und durchläuft sie. Für jeden Aufzählungswert erstellt sie ein enum_item mit dem Namen und dem Wert des Aufzählungswerts, wobei sie std::meta::identifier_of verwendet, um den Namen zu erhalten, und std::meta::extract, um den Wert zu erhalten. Das resultierende Array wird dann zurückgegeben.

Die MyEnum-Aufzählung wird mit vier Aufzählungselementen definiert: y, x, e (explizit auf -1 gesetzt) und z (explizit auf 99 gesetzt). Diese Aufzählung wird als Beispiel verwendet, um die Metaprogrammierungsfunktionen zu demonstrieren.

In der main-Funktion gibt der Code zunächst eine neue Zeile zu Formatierungszwecken aus. Anschließend wird der Name des Aufzählungstyps MyEnum mithilfe von std::meta::identifier_of ausgegeben. Als Nächstes wird get_enum_data<MyEnum>() aufgerufen, um das Array der enum_item-Strukturen für MyEnum abzurufen und dieses Array zu durchlaufen. Für jedes enum_item werden der Name und der Wert des Aufzählungselements ausgegeben. Der Wert wird für eine konsistente Ausgabeformatierung in long umgewandelt. Schließlich wird zur Formatierung ein weiterer Zeilenumbruch ausgegeben.

Insgesamt zeigt dieser Code, wie Reflection zur Compile-Zeit verwendet werden kann, um Aufzählungstypen zu untersuchen und nützliche Metadaten wie Aufzählungsnamen und -werte zu generieren, die dann zur Laufzeit verwendet werden können.

Hier ist die Ausgabe des Programms:

Diese Erklärung wurde mit dem KI-Tool Copilot von Microsoft erstellt. Ich muss zugeben, dass ich ziemlich beeindruckt war, weil das beschriebene Feature brandneu ist.

Das folgende Programm zeigt zwei Möglichkeiten, auf die Elemente einer Klasse zuzugreifen: über den Index und den Namen.

// reflectionClass.cpp

#include <experimental/meta>
#include <iostream>

struct Base { 
    int i{}; 
    void inc(int& j){ j++; }
};

consteval auto number(int n) {
  //return std::meta::nonstatic_data_members_of(^Base)[n];
  return std::meta::members_of(^Base)[n];
}


consteval auto named(std::string_view name) {
  for (std::meta::info field : std::meta::members_of(^Base)) {
    if (std::meta::has_identifier(field) && std::meta::identifier_of(field) == name)
      return field;
  } 
  return std::meta::info{};
}


int main() { 

  std::cout << '\n';

  Base base;
  base.[:number(0):] = 1;  
  std::cout << "base.i= " << base.i << '\n';
  base.[:number(1):](base.i);
  std::cout << "base.i= " << base.i << '\n';

  std::cout << '\n';
 
  base.[:named("i"):] = 3;
  std::cout << "base.i= " << base.i << '\n';
  base.[:named("inc"):](base.i);
  std::cout << "base.i= " << base.i << '\n';

}

Base ist die Klasse, über die ich nachdenken möchte. Die Metafunktionen „number“ und „named“ liefern mir die erforderlichen Informationen.

  • "number": std::meta::members_of(^Base)[n] gibt das Element n von Base zurück. Im Gegensatz dazu gibt "number": std::meta::nonstatic_data_members_of(^Base)[n] das nicht statische Datenelement n von Base zurück. Die Reflection-Bibliothek verfügt auch über eine Metafunktion, mit der alle statischen Datenelemente einer Klasse abgerufen werden können: std::meta::static_data_members_of(^Base)[n]
  • namend: iteriert durch alle Elemente von Base und gibt das Element mit dem Namen name zurück: std::meta::identifier_of(field) == name

Ich springe nun zum Hauptprogramm. Das Element i von Base wird erhöht. Entweder durch Zuweisung des Wertes oder durch Aufruf der Funktion inc. Die Verwendung des falschen Index oder Namens führt zu einem Fehler zur Compilezeit:[:member_number(10):] = 1).

Abschließend zeige ich hier die Ausgabe des Programms:

In meinem nächsten Artikel spiele ich weiter mit Reflection in C++26. (rme)