C++26 Static Reflection Complete Guide | Compile-Time Type Information
이 글의 핵심
Query type information at compile time with zero runtime overhead using C++26 Static Reflection. From basic syntax to serialization, code generation, and practical patterns.
Introduction
C++26’s Static Reflection is rated as “the biggest upgrade since template invention”. Previously, obtaining type information required macros, SFINAE, complex template metaprogramming, but now it can be simply queried with standard library functions. This guide explains Static Reflection basic syntax, practical usage patterns (serialization, ORM, code generation), and comparison with existing approaches with code examples. Prerequisites:
- C++ template basics (Template Guide)
- Understanding constexpr functions
- Metaprogramming concepts
Reality in Production
When learning development, everything seems clean and theoretical. But production is different. Wrestling with legacy code, chasing tight deadlines, facing unexpected bugs. The content covered here was initially learned as theory, but through applying it to real projects, I realized “ah, that’s why it’s designed this way.”
Table of Contents
- What is Static Reflection?
- Basic Syntax
- Type Query
- Member Iteration
- Practice: Auto Serialization
- Practice: ORM Mapping
- Practice: Code Generation
- Comparison with Existing Approaches
- Performance and Constraints
- Conclusion
What is Static Reflection?
Concept
Reflection is the ability of a program to inspect and manipulate its own structure (types, members, functions, etc.). C++26 Static Reflection characteristics:
- Compile-time only: All operations performed at compile time
- Zero overhead: No runtime performance impact
- Type safe: Compiler detects all errors
- Standard library: Provides
<meta>header
Limitations of Existing Approaches
Macro-based (C++03 style):
#define REFLECT_STRUCT(Type, ...) \
/* Complex macro magic */
REFLECT_STRUCT(Person, name, age, email);
- Difficult to debug
- No type safety
- Poor IDE support SFINAE/enable_if (C++11 style):
template<typename T>
auto serialize(const T& obj)
-> decltype(obj.name, void()) {
// Compiles only when name member exists
}
- Hard to read
- Complex error messages
- Difficult to maintain C++26 Static Reflection:
template<typename T>
std::string serialize(const T& obj) {
std::string result = "{";
[:expand(nonstatic_data_members_of(^T)):] {
result += std::format("\"{}\": {}, ",
identifier_of(^^[::])),
obj.[::]
);
}
return result + "}";
}
- Easy to read
- Type safe
- Compiler optimization possible
Basic Syntax
Reflection Operator: ^
^ operator converts type or expression to reflection:
#include <meta>
struct Person {
std::string name;
int age;
};
// Type reflection
constexpr auto person_reflection = ^Person;
// Member reflection
constexpr auto name_reflection = ^Person::name;
// Expression reflection
int x = 42;
constexpr auto x_reflection = ^x;
Splicer: [: :]
[: :] converts reflection back to code:
// Get type name
constexpr auto type_name = identifier_of(^Person);
// type_name == "Person"
// Convert to type
using PersonType = [:^Person:]; // Same as Person
// Member access
Person p{"Alice", 30};
int age = p.[: ^Person::age :]; // Same as p.age
Basic Query Functions
#include <meta>
#include <iostream>
struct Point {
double x;
double y;
void print() const {
std::cout << "(" << x << ", " << y << ")\n";
}
};
int main() {
// Type name
std::cout << identifier_of(^Point) << '\n'; // "Point"
// Member count
constexpr auto members = nonstatic_data_members_of(^Point);
std::cout << members.size() << '\n'; // 2
// Member function count
constexpr auto methods = member_functions_of(^Point);
std::cout << methods.size() << '\n'; // 1
// Print member names
[:expand(members):] {
std::cout << identifier_of(^^[::]) << '\n';
}
// Output: x, y
}
Member Iteration
Print All Members
#include <meta>
#include <iostream>
template<typename T>
void print_members(const T& obj) {
std::cout << identifier_of(^T) << " {\n";
[:expand(nonstatic_data_members_of(^T)):] {
std::cout << " " << identifier_of(^^[::])
<< " = " << obj.[::] << '\n';
}
std::cout << "}\n";
}
struct Config {
std::string host = "localhost";
int port = 8080;
bool ssl = false;
};
int main() {
Config config;
print_members(config);
// Output:
// Config {
// host = localhost
// port = 8080
// ssl = 0
// }
}
Practice: Auto Serialization
JSON Serialization
#include <meta>
#include <string>
#include <sstream>
template<typename T>
std::string to_json(const T& obj) {
std::ostringstream oss;
oss << "{";
bool first = true;
[:expand(nonstatic_data_members_of(^T)):] {
if (!first) oss << ", ";
first = false;
oss << "\"" << identifier_of(^^[::]) << "\": ";
// Handle different types
if constexpr (std::is_same_v<decltype(obj.[::]), std::string>) {
oss << "\"" << obj.[::] << "\"";
} else {
oss << obj.[::];
}
}
oss << "}";
return oss.str();
}
struct User {
int id;
std::string name;
int age;
};
int main() {
User user{1, "Alice", 30};
std::cout << to_json(user) << '\n';
// {"id": 1, "name": "Alice", "age": 30}
}
Practice: ORM Mapping
SQL Query Generation
template<typename T>
std::string generate_insert_query(const T& obj) {
std::string table_name = identifier_of(^T);
std::string columns = "";
std::string values = "";
bool first = true;
[:expand(nonstatic_data_members_of(^T)):] {
if (!first) {
columns += ", ";
values += ", ";
}
first = false;
columns += identifier_of(^^[::]);
values += std::format("'{}'", obj.[::]);
}
return std::format("INSERT INTO {} ({}) VALUES ({})",
table_name, columns, values);
}
struct Product {
int id;
std::string name;
double price;
};
int main() {
Product product{1, "Laptop", 999.99};
std::cout << generate_insert_query(product) << '\n';
// INSERT INTO Product (id, name, price) VALUES ('1', 'Laptop', '999.99')
}
Comparison with Existing Approaches
Before vs After
Before (Macro-based):
#define SERIALIZE_STRUCT(Type, ...) \
/* 100+ lines of complex macro magic */
SERIALIZE_STRUCT(User, id, name, email);
After (Reflection):
// Single generic function for all types
template<typename T>
std::string serialize(const T& obj) {
return to_json(obj); // Works for any struct
}
Code Reduction
| Task | Before | After | Reduction |
|---|---|---|---|
| Serialization | 100+ lines | 10 lines | 90% |
| ORM mapping | 200+ lines | 20 lines | 90% |
| Debug output | 50+ lines | 5 lines | 90% |
Compiler Support
Current Status (March 2026)
| Compiler | Version | Support Status |
|---|---|---|
| GCC | 14+ | Experimental (-std=c++2c -freflection) |
| Clang | 19+ | Partial (-std=c++2c) |
| MSVC | TBD | In development |
Usage
# GCC
g++ -std=c++2c -freflection source.cpp -o output
# Clang
clang++ -std=c++2c source.cpp -o output
Conclusion
C++26 Static Reflection is a feature that changes the metaprogramming paradigm: Key Advantages:
- Readability: Complex template tricks → Clear code
- Zero overhead: Compile-time only
- Type safety: Compiler validation
- Productivity: Auto-generate boilerplate code Main Use Cases:
- Serialization/deserialization (JSON, Binary, XML)
- ORM, database mapping
- RPC, network protocols
- Test frameworks
- Code generators Getting Started:
- Install GCC 14+ or Clang 19+
- Start with simple
to_stringfunction - Convert project serialization code to Reflection
- Gradually expand application Next Learning:
- C++ Template Advanced
- C++ Concepts
- C++ Metaprogramming References:
- P2996: Reflection for C++26
- C++26 Feature Complete
- GCC Reflection Documentation