File: p2996-reflection.md

package info (click to toggle)
glaze 7.2.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 10,316 kB
  • sloc: cpp: 170,219; sh: 109; ansic: 26; makefile: 12
file content (391 lines) | stat: -rw-r--r-- 12,491 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# C++26 P2996 Reflection Support

Glaze supports [P2996 "Reflection for C++26"](https://wg21.link/P2996) as an alternative reflection backend. When enabled, P2996 reflection replaces the traditional `__PRETTY_FUNCTION__` parsing and structured binding tricks with proper compile-time reflection primitives.

## Overview

P2996 was voted into C++26 in June 2025 and provides standardized compile-time reflection capabilities including:
- Querying type metadata at compile time
- Iterating over struct members
- Getting member names and types
- Splicing reflected entities back into code

When `GLZ_REFLECTION26` is enabled, Glaze uses P2996 for:
- `count_members<T>` - counting struct fields
- `to_tie(T&)` - creating a tuple of references to members
- `member_nameof<N, T>` - getting the name of the Nth member
- `member_names<T>` - array of all member names
- `type_name<T>` - getting the type name as a string

The entire Glaze API remains unchanged - JSON, BEVE, CSV, and all other formats work exactly as before.

## Requirements

P2996 reflection requires a compiler with C++26 reflection support:

- **GCC 16+**: Reflection support merged into GCC trunk
  - Available via the [Ubuntu Toolchain PPA](https://launchpad.net/~ubuntu-toolchain-r/+archive/ubuntu/ppa) or by building from source
  - See [GCC 16 changes](https://gcc.gnu.org/gcc-16/changes.html) for the full list of supported reflection proposals
- **Bloomberg clang-p2996**: Experimental Clang fork with P2996 support
  - Repository: https://github.com/bloomberg/clang-p2996
  - Docker image: `vsavkov/clang-p2996:amd64`

### Compiler Flags

**GCC 16+:**
```bash
g++-16 -std=c++26 -freflection
```

**Bloomberg clang-p2996:**
```bash
clang++ -std=c++26 -freflection -fexpansion-statements -stdlib=libc++
```

| Flag | Purpose |
|------|---------|
| `-std=c++26` | Enable C++26 mode |
| `-freflection` | Enable P2996 reflection |
| `-fexpansion-statements` | Enable expansion statements (Bloomberg Clang only) |
| `-stdlib=libc++` | Required for `<meta>` header (Bloomberg Clang only) |

## Enabling P2996 Support

### Option 1: CMake (Recommended)

```cmake
set(glaze_ENABLE_REFLECTION26 ON)
FetchContent_MakeAvailable(glaze)
```

### Option 2: Compiler Define

```bash
clang++ -DGLZ_REFLECTION26=1 -std=c++26 -freflection ...
```

### Option 3: Automatic Detection

If your compiler defines `__cpp_lib_reflection` or `__cpp_impl_reflection`, Glaze automatically enables P2996 support.

## Feature Detection

Check if P2996 is enabled at compile time:

```cpp
#include "glaze/core/feature_test.hpp"

#if GLZ_REFLECTION26
// P2996 reflection is available
#endif

// Or use the constexpr variable
if constexpr (glz::has_reflection26) {
    // P2996 code path
}
```

## Usage Example

The API is identical whether using P2996 or traditional reflection:

```cpp
#include "glaze/glaze.hpp"

struct Person {
    std::string name;
    int age;
    double height;
};

int main() {
    // JSON serialization works the same
    Person p{"Alice", 30, 1.65};
    std::string json = glz::write_json(p).value_or("error");
    // {"name":"Alice","age":30,"height":1.65}

    // Member names reflection
    constexpr auto names = glz::member_names<Person>;
    // names == {"name", "age", "height"}

    // Count members
    constexpr auto count = glz::detail::count_members<Person>;
    // count == 3

    // Type name
    constexpr auto type = glz::type_name<Person>;
    // type == "Person"

    // to_tie for member access
    auto tie = glz::to_tie(p);
    glz::get<0>(tie) = "Bob";  // Modifies p.name
}
```

## Benefits of P2996

| Feature | Traditional | P2996 |
|---------|-------------|-------|
| Max struct members | 128 | Unlimited |
| Non-aggregate types | Not supported | Full support |
| Inheritance | Requires explicit `glz::meta` | Automatic (via `nonstatic_data_members_of`) |
| Member name extraction | `__PRETTY_FUNCTION__` parsing | `std::meta::identifier_of` |
| Member count | Structured binding probe | `nonstatic_data_members_of().size()` |
| Private member access | Limited | Full (with `access_context::unchecked()`) |
| Compile-time safety | Compiler-specific hacks | Standardized API |

### Unlimited Member Count

Traditional reflection uses a compile-time binary search with structured bindings, limiting structs to 128 members. P2996 has no such limitation:

```cpp
// Works with P2996, would fail with traditional reflection
struct LargeStruct {
    int field1, field2, /* ... */ field200;
};

constexpr auto count = glz::detail::count_members<LargeStruct>;
// count == 200 (with P2996)
```

### Non-Aggregate Type Support

Traditional reflection requires types to be aggregates (no user-defined constructors, no private members, no virtual functions, no base classes). P2996 removes this limitation:

```cpp
// Classes with custom constructors
class ConstructedClass {
public:
    std::string name;
    int value;

    ConstructedClass() : name("default"), value(0) {}
    ConstructedClass(std::string n, int v) : name(std::move(n)), value(v) {}
};

// Works with P2996!
std::string json;
glz::write_json(ConstructedClass{"test", 42}, json);
// {"name":"test","value":42}

// Classes with private members (using glz::meta for access)
class PrivateMembers {
    std::string secret;
    int hidden;
public:
    PrivateMembers(std::string s, int h) : secret(std::move(s)), hidden(h) {}
    friend struct glz::meta<PrivateMembers>;
};

template <>
struct glz::meta<PrivateMembers> {
    using T = PrivateMembers;
    static constexpr auto value = object(&T::secret, &T::hidden);
};

// Classes with virtual functions
class VirtualClass {
public:
    std::string name;
    virtual ~VirtualClass() = default;
    virtual void do_something() {}
};

// Works with P2996!
constexpr auto count = glz::detail::count_members<VirtualClass>;
// count == 1 (only 'name', virtual function table pointer is not counted)

// Derived classes - base class members are automatically included!
class Base {
public:
    std::string name;
    int id;
    Base() : name("base"), id(0) {}
};

class Derived : public Base {
public:
    std::string extra;
    Derived() : Base(), extra("derived") {}
};

// No glz::meta needed! P2996 automatically includes base class members
std::string json;
glz::write_json(Derived{}, json);
// {"name":"base","id":0,"extra":"derived"}

// Member names include inherited members (base first, then derived)
constexpr auto names = glz::member_names<Derived>;
// names == {"name", "id", "extra"}

constexpr auto count = glz::detail::count_members<Derived>;
// count == 3 (2 from Base + 1 from Derived)
```

### Automatic Enum String Serialization

With P2996 enabled, enums can be automatically serialized as strings without writing any `glz::meta` specializations. This uses the `reflect_enums` option.

Since `reflect_enums` is not part of the base `glz::opts`, you enable it by creating a custom opts struct:

```cpp
struct reflect_enums_opts : glz::opts {
   bool reflect_enums = true;
};

enum class Color { Red, Green, Blue };

Color c = Color::Green;

// Write enum as string
auto json = glz::write<reflect_enums_opts{}>(c).value_or("error");
// json == "\"Green\""

// Read string back to enum
Color c2;
glz::read<reflect_enums_opts{}>(c2, json);
// c2 == Color::Green
```

Enums in structs also work:

```cpp
struct Pixel {
   int x;
   int y;
   Color color;
};

Pixel p{10, 20, Color::Blue};
auto json = glz::write<reflect_enums_opts{}>(p).value_or("error");
// {"x":10,"y":20,"color":"Blue"}
```

P2996 also provides `enum_to_string` and `string_to_enum` utility functions:

```cpp
constexpr auto name = glz::enum_to_string(Color::Red);
// name == "Red"

constexpr auto value = glz::string_to_enum<Color>("Blue");
// value == Color::Blue

constexpr auto invalid = glz::string_to_enum<Color>("Invalid");
// invalid == std::nullopt
```

> **Note:** Without the `reflect_enums` option, enums without `glz::meta` specializations are still serialized as their underlying integer values, even when P2996 is enabled.

### Cleaner Type Names

P2996 provides cleaner type names via `std::meta::display_string_of`:

```cpp
// Traditional: might return "Person" or "struct Person" depending on compiler
// P2996: returns "Person" consistently
constexpr auto name = glz::type_name<Person>;
```

> **Breaking Change:** P2996 `type_name` returns unqualified names without namespace prefixes, while traditional reflection includes them:
> - Traditional: `"mylib::MyEnum"`
> - P2996: `"MyEnum"`
>
> Code that depends on the exact format of type names (e.g., for key generation in `rename_key`) may produce different output.

### Qualified Type Names Option

> **Note:** The `qualified_type_names` option is prepared for future P2996 implementations. Bloomberg clang-p2996 does not yet support `std::meta::qualified_name_of`, so this option currently has no effect with P2996. Traditional reflection always returns qualified names regardless of this option.

The `qualified_type_names` option and associated functions (`type_name_for_opts`, `name_for_opts`) are available for forward compatibility:

```cpp
struct my_opts : glz::opts {
    bool qualified_type_names = true;
};

// These functions are ready for when qualified_name_of becomes available
constexpr auto name = glz::type_name_for_opts<mylib::MyType, my_opts{}>();
constexpr auto name2 = glz::name_for_opts<mylib::MyType, my_opts{}>();
```

When `std::meta::qualified_name_of` is added to the P2996 implementation, these functions will automatically support returning fully-qualified type names with namespace prefixes.

## Docker Development Environment

A Docker container with Bloomberg clang-p2996 can be used for development and testing:

```dockerfile
FROM ubuntu:22.04

# Install build dependencies
RUN apt-get update && apt-get install -y \
    git cmake ninja-build python3

# Clone and build Bloomberg clang-p2996
RUN git clone https://github.com/bloomberg/clang-p2996.git /opt/llvm-project
WORKDIR /opt/llvm-project
RUN cmake -S llvm -B build -G Ninja \
    -DCMAKE_BUILD_TYPE=Release \
    -DLLVM_ENABLE_PROJECTS="clang" \
    -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi"
RUN cmake --build build
```

### Running Tests in Docker

```bash
docker run --rm -v $(pwd):/glaze -w /glaze glaze-p2996-test \
    clang++ -std=c++26 -freflection -fexpansion-statements -stdlib=libc++ \
    -I/glaze/include -DGLZ_REFLECTION26=1 \
    -Wl,-rpath,/opt/llvm-project/build/lib/aarch64-unknown-linux-gnu \
    -o /tmp/test tests/p2996_test/p2996_json_test.cpp && /tmp/test
```

## Implementation Details

### How P2996 Reflection Works

The P2996 implementation uses these key primitives:

```cpp
// Reflect on a type to get meta-info
constexpr auto type_info = ^^Person;

// Get all non-static data members
constexpr auto members = std::meta::nonstatic_data_members_of(
    ^^Person,
    std::meta::access_context::unchecked()
);

// Get member name
constexpr auto name = std::meta::identifier_of(members[0]);
// name == "name"

// Splice to access member
Person p;
p.[:members[0]:] = "Alice";  // Sets p.name
```

### Access Context

Glaze uses `access_context::unchecked()` to reflect on all members regardless of access specifiers. This allows P2996 automatic reflection to access private members without requiring friend declarations.

> **Note:** If you use explicit `glz::meta` specializations with pointer-to-member syntax (e.g., `&T::private_member`), friend declarations are still required because C++ pointer-to-member respects access control. The `access_context::unchecked()` bypass only applies to P2996 automatic reflection.

## Compatibility Notes

- P2996 support is **opt-in** and does not affect builds using standard compilers
- All existing `glz::meta` specializations continue to work
- The `glz::reflectable<T>` and `glz::has_reflect<T>` concepts work identically
- Custom serializers (`glz::to<JSON>`, `glz::from<JSON>`) are unaffected

## Future

As C++26 compilers mature and P2996 becomes widely available, it will become the preferred reflection mechanism. The traditional `__PRETTY_FUNCTION__` approach will remain for backward compatibility with C++23 compilers.

## See Also

- [Reflection in Glaze](reflection.md) - General reflection documentation
- [Pure Reflection](pure-reflection.md) - Automatic struct reflection without metadata
- [Modify Reflection](modify-reflection.md) - Customizing reflected member names
- [Automatic Enum String Serialization](enum-reflection.md) - Enum reflection via external libraries