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.

In Pocket speichern vorlesen Druckansicht
HolzwĂĽrfel mit dem Schriftzug C++

(Bild: SerbioVas/Shutterstock)

Lesezeit: 4 Min.
Von
  • Rainer Grimm
Inhaltsverzeichnis

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.

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++.

Meine Beispiele basieren auf dem Reflection-Proposal P2996R5.

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.

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);​

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)