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.
- Rainer Grimm
Nach der Einführung zu Reflexion in C++26 und einer grundlegenden Einführung der Metafunktionen, geht es diesmal um Enums und Klassen.
Die Namen der Metafunktionen für den Zugriff auf die Elemente einer Enum oder einer Klasse sind oft identisch.
Zugriff auf die Elemente einer Enum
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:
Mein kleines Experiment
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.
Zugriff auf die Elemente einer Klasse
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 Elementn
vonBase
zurück. Im Gegensatz dazu gibt "number": std::meta::nonstatic_data_members_of(^Base)[n]
das nicht statische Datenelementn
vonBase
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 vonBase
und gibt das Element mit dem Namenname
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:
Wie geht es weiter?
In meinem nächsten Artikel spiele ich weiter mit Reflection in C++26. (rme)