Reflection in C++26: Determine the Layout

Thanks to reflection, you can determine the layout of types.

My examples are based on the reflection proposal P2996R5.

Class Layout

The following program determines the class layout of a few members.

// 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';

}

The C++ program reflects on the layout of a struct’s data members. The main goal of this code is to determine and print the memory offsets and sizes of each member within a struct.

The first part of the code defines a std::array named layout, which is intended to store descriptors for each member of the struct. These descriptors include the offset and size of each member. The [: expand(nonstatic_data_members_of(^S)) :] construct is a placeholder for a metaprogramming construct that iterates over the non-static data members of the struct S. This construct is only a temporary workaround and is followed by a lambda function that captures the current state by reference (&) and initializes an index variable i to zero. The lambda function is then applied to each data member, storing the offset and size of each member in the layout array and incrementing the index i.

The struct X is defined with three data members: a char named a, an int named b, and a double named c. These members are used to demonstrate the reflection mechanism.

In the main calls a function get_layout<X>(), which returns the layout array filled with the offsets and sizes of the members of struct X. The program then prints the layout of struct X by iterating over the layout array and printing the offset and size of each member.

This code reflects on the memory layout of a struct’s data members in C++, which can be useful for understanding memory alignment and optimizing data structures.

Finally, he has the output of the program:

You can store reflections in a container or apply an algorithm to them.

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

    Size

    The following program determines the size of a few built-in types.

    // 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';
        
    }
    

    The program begins by including several headers: <experimental/meta> for reflection capabilities, <array> for fixed-size array support, <iostream> for input/output operations, <ranges> for range-based algorithms, and <algorithm> for general algorithms.

    The constexpr std::array types declaration creates a compile-time array containing reflections for int, float, and double. These reflections are represented using the reflection operator ^.

    Next, the constexpr std::array sizes declaration defines another compile-time array that will hold the sizes of the types specified in the types array. This array is initialized using a lambda function that creates an array r of the same size as types. The std::ranges::transform function is then used to populate r by applying the std::meta::size_of operation to each reflection value in types. The std::meta::size_of operation is a metafunction that returns the size of a type at compile time.

    The main function begins by entering a loop that iterates over the indices of the types array. For each index, it prints the size of the corresponding type from the sizes array in bytes.

    Instead of the ranges function std::ranges::transform, you could also use the classical transform algorithm;

    std::transform(types.begin(), types.end(), r.begin(), std::meta::size_of);
    

    What’s Next?

    This was my first dive into reflection in C++26. A deeper dive will follow. In my next post, I will focus on contracts.

    Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.

    Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,