Reflection in C++26: Layout der Datentypen bestimmen
Der Blogbeitrag fĂĽhrt das Thema Reflection in C++26 fort und zeigt, wie man das Layout der Datentypen bestimmen kann.

(Bild: SerbioVas/Shutterstock)
- Rainer Grimm
Nach der Einführung zu Reflexion in C++26, einer grundlegenden Einführung der Metafunktionen und der Übersicht zu Enums und Klassen geht es diesmal darum, wie sich Reflection nutzen lässt, um das Layout von Datentypen zu bestimmen.
Meine Beispiele basieren auf dem Reflection-Proposal P2996R5.
Blick auf das Klassenlayout
Das folgende Programm bestimmt das Klassenlayout einiger Mitglieder.
// classLayout.cpp
#include <experimental/meta>
#include <iostream>
#include <utility>
#include <vector>
#include <array>
struct member_descriptor
{
std::size_t offset;
std::size_t size;
bool operator==(member_descriptor const&) const = default;
};
// returns std::array<member_descriptor, N>
// The company's biggest funding.
template <typename S>
consteval auto get_layout() {
constexpr size_t N = []() consteval {
return nonstatic_data_members_of(^S).size();
}();
std::array<member_descriptor, N> layout;
[: expand(nonstatic_data_members_of(^S)) :]
>> [&, i=0]<auto e>() mutable {
layout[i] = {.offset=offset_of(e), .size=size_of(e)};
++i;
};
return layout;
}
struct X
{
char a;
int b;
double c;
};
int main() {
std::cout << '\n';
constexpr auto layout = get_layout<X>();
std::cout << "Layout of struct X:\n";
for (const auto& member : layout) {
std::cout << "Offset: " << member.offset
<< ", Size: " << member.size << '\n';
}
std::cout << '\n';
}
Das C++-Programm reflektiert über das Layout der Datenelemente einer Struktur. Das Hauptziel dieses Codes ist es, die Speicher-Offsets und Größen der einzelnen Mitglieder einer Struktur zu ermitteln und auszugeben.
Der erste Teil des Codes definiert ein std::array
namens layout
, in dem Deskriptoren für jedes Mitglied der Struktur gespeichert werden sollen. Diese Deskriptoren enthalten den Offset und die Größe der einzelnen Elemente. Das [: expand(nonstatic_data_members_of(^S)) :]
Konstrukt ist ein Platzhalter fĂĽr ein Metaprogrammierungskonstrukt, das ĂĽber die nicht statischen Datenmitglieder der Struct S
iteriert. Dieses Konstrukt ist nur eine vorübergehende Notlösung und wird von einer Lambda-Funktion gefolgt, die den aktuellen Zustand per Referenz (&
) erfasst und eine Indexvariable i
auf Null initialisiert. Die Lambda-Funktion wird dann auf jedes Datenelement angewendet, wobei der Offset und die Größe jedes Elements im Layout-Array gespeichert und der Index i
erhöht wird.
Die Struktur X
wird mit drei Datenmitgliedern definiert: einem char namens a
, einem int
namens b
und einem double
namens c.
Diese Mitglieder werden zur Demonstration des Reflection-Mechanismus verwendet.
Im main
wird eine Funktion get_layout<X>()
aufgerufen, die das Layout-Array mit den Offsets und Größen der Mitglieder von Struct X
zurĂĽckgibt. Das Programm gibt dann das Layout von Struct X
aus, indem es über das Layout-Array iteriert und den Offset und die Größe jedes Mitglieds ausgibt.
Dieser Code reflektiert das Speicherlayout der Datenelemente einer Struktur in C++, was für das Verständnis der Speicherausrichtung und die Optimierung von Datenstrukturen nützlich sein kann.
SchlieĂźlich sieht die Ausgabe des Programms folgendermaĂźen aus:
Man kann Reflexionen in einem Container speichern oder einen Algorithmus auf sie anwenden.
Blick auf die Größe der Datentypen
Das folgende Programm ermittelt die Größe einiger built-in-Datentypen.
// getSize.cpp
#include <experimental/meta>
#include <array>
#include <iostream>
#include <ranges>
#include <algorithm>
constexpr std::array types = {^int, ^float, ^double};
constexpr std::array sizes = []{
std::array<std::size_t, types.size()> r;
std::ranges::transform(types, r.begin(), std::meta::size_of);
return r;
}();
int main() {
std::cout << '\n';
std::cout << "Types and their sizes:\n";
for (std::size_t i = 0; i < types.size(); ++i) {
std::cout << "Size: " << sizes[i] << " bytes\n";
}
std::cout << '\n';
}
Das Programm beginnt mit dem EinfĂĽgen mehrerer Header: <experimental/meta>
für Reflection-Fähigkeiten, <array>
für die Unterstützung von Arrays fester Größe, <iostream>
fĂĽr Ein-/Ausgabeoperationen, <ranges>
fĂĽr range-based Algorithmen und <algorithm>
fĂĽr allgemeine Algorithmen.
Die Deklaration constexpr std::array types
erzeugt zur Compile-Zeit ein Array mit Reflections fĂĽr int
, float
und double
. Diese Reflexionen werden mit dem Reflection-Operator ^
dargestellt.
Als Nächstes definiert die Deklaration constexpr std::array sizes
ein weiteres Compilezeit-Array, das die Größen der im Array types
angegebenen Typen enthält. Dieses Array wird mit einer Lambda-Funktion initialisiert, die ein Array r
mit der gleichen Größe wie types
erstellt. Die Funktion std::ranges::transform
wird dann verwendet, um r
aufzufĂĽllen, indem die Operation std::meta::size_of
auf jeden Reflection-Wert in types
angewendet wird. Die std::meta::size_of-
Operation ist eine Metafunktion, die die Größe eines Datentyps zur Compile-Zeit zurückgibt.
Die main
-Funktion beginnt mit einer Schleife, die über die Indizes des Datentypen-Arrays iteriert. Für jeden Index gibt sie die Größe des entsprechenden Datentyps aus dem Größen-Array in Bytes aus.
Anstelle der Ranges-Funktion std::ranges::transform
kann man den klassischen transform-
Algorithmus verwenden.
std::transform(types.begin(), types.end(), r.begin(), std::meta::size_of);​
Wie geht es weiter?
Die letzten Blogbeiträge waren ein erster Überblick zu Reflection in C++26. Ein tieferer Einblick wird später folgen. In meinem nächsten Artikel werde ich mich auf Contracts konzentrieren. (rme)