Reflection in C++26: Determine the Layout

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.





?

Modernes C++ Mentoring

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.

Michael Becker

Senior Software Developer at ModuleWorks

1 个月

I feel like this is the one feature everyone is waiting for. How often did I had this conversation, which at some point goes like ".. and when we have reflections, this is no problem anymore". We will see whether Reflection lives up to this expectation. ??

nicolas duminil

Senior Software Architect, Microservices/Cloud Native Expert

1 个月

The C++ world discovers, at the occasion of its 26 release, the introspection that Java developers try to avoid since decades, after having used it more than reasonably, in frameworks like Spring. Choosing C++ to do introspection in a moment when stacks like Quarkus use AOT (Ahead of Time) compilation and exclude it, definitely seems to me like a bad choice.

要查看或添加评论,请登录

社区洞察

其他会员也浏览了