C++26 Static Reflection Complete Guide | Compile-Time Type Information

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

  1. What is Static Reflection?
  2. Basic Syntax
  3. Type Query
  4. Member Iteration
  5. Practice: Auto Serialization
  6. Practice: ORM Mapping
  7. Practice: Code Generation
  8. Comparison with Existing Approaches
  9. Performance and Constraints
  10. 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

#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

TaskBeforeAfterReduction
Serialization100+ lines10 lines90%
ORM mapping200+ lines20 lines90%
Debug output50+ lines5 lines90%

Compiler Support

Current Status (March 2026)

CompilerVersionSupport Status
GCC14+Experimental (-std=c++2c -freflection)
Clang19+Partial (-std=c++2c)
MSVCTBDIn 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:
  1. Install GCC 14+ or Clang 19+
  2. Start with simple to_string function
  3. Convert project serialization code to Reflection
  4. Gradually expand application Next Learning: