Reflection in C++26

After the search into the breadth starts today, the search into the depth: reflection.

Reflection

Reflection is the ability of a program to examine, introspect, and modify its structure and behavior.

Reflection in C++ is more. Here are two statements from the proposal (P2996R5).

We not only want to observe the structure of the program: We also want to ease generating code that depends on those observations. That combination is sometimes referred to as “reflective metaprogramming”, but within WG21 discussion the term “reflection” has often been used informally to refer to the same general idea.

This proposal is not intended to be the end-game as far as reflection and compile-time metaprogramming are concerned. Instead, we expect it will be a useful core around which more powerful features will be added incrementally over time. In particular, we believe that most or all the remaining features explored in P1240R2 and that code injection (along the lines described in [P2237R0]) are desirable directions to pursue.

History

The history of reflection in C++ is based on template metaprogramming. Template metaprogramming started around 1994, and so does reflection. C++98 got runtime reflection (RTTI) and function template type deduction. The type traits library in C++11 improved C++ capabilities. In C++26, we will probably get general reflection support.

My Strategy

I will base my reflection presentation in C++26 on proposal P2996R and use examples from it.

First, let me jump forward and back from the reflection value to the grammatical elements.

Grammatical Elements <=> Reflection Value

The following program starts in the grammatical domain, jumps to the reflection domain, and back to the grammatical domain.

// forthAndBack.cpp (P2996R5)

#include <iostream>
#include <cassert>
#include <concepts>

int main() {
    constexpr auto r = ^int;
    typename[:r:] x = 42;       // Same as: int x = 42;
    typename[:^char:] c = '*';  // Same as: char c = '*';

    static_assert(std::same_as<decltype(x), int>);
    static_assert(std::same_as<decltype(c), char>);
    assert(x == 42);
    assert(c == '*');
}
  • ^: Reflection Operator creates a reflection value from its operand (^int and ^char)
  • [: refl :]: Splicer creates a grammatical element from a reflection value ([:r:] and [:^char:])
  • Reflection Value is a representation of program elements as a constant expression

The call ^gramOper creates a reflection value with an opaque reflection type: std::meta::info. std::same_as is a concept.

 

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.

     

    Jumping between the grammatical and reflection domains makes no sense on its own. Let’s do something meaningful. Let’s analyze the program enumString.cpp deeper.

    Enum <=> String

    Enum => String

    The following example converts an enum value to a string.

    // enumString.cpp
    
    #include <iostream>
    #include <experimental/meta>
    #include <string>
    #include <type_traits>
    
    
    template<typename E>
      requires std::is_enum_v<E>                      // (1)
    constexpr std::string enum_to_string(E value) {
      std::string result = "<unnamed>";
      [:expand(std::meta::enumerators_of(^E)):] >>    // (2)
      [&]<auto e>{
        if (value == [:e:]) {
          result = std::meta::identifier_of(e);       // (3)
        }
      };
      return result;
    }
    
    
    int main() {
    
        std::cout << '\n';
    
        enum Color { red, green, blue };
        std::cout << "enum_to_string(Color::red): " << enum_to_string(Color::red) << '\n';
        // std::cout << "enum_to_string(42): " << enum_to_string(42) << '\n'; 
    
        std::cout << '\n';
    
    }
    

    Line (1) checks if value is an enumerator using the type trait std::is_enum. The expression ^E in line (2) produces the reflection value. You can ignore the function expand in the same line. The expansion statements are missing in the current implementation.

    The functions std::meta::enumerators_of and std::meta::enumerators_of in lines (2 and 3) are metafunctions. The metafunctions can only run at compile time because they are declared as consteval.

    Here are a few of them:

    namespace std::meta {
      consteval auto members_of(info type_class) -> vector<info>;
      consteval auto bases_of(info type_class) -> vector<info>;
    
      consteval auto static_data_members_of(info type_class) -> vector<info>;
      consteval auto nonstatic_data_members_of(info type_class) -> vector<info>;
    
      consteval auto subobjects_of(info type_class) -> vector<info> {
        auto subobjects = bases_of(type_class);
        subobjects.append_range(nonstatic_data_members_of(type_class));
        return subobjects;
      }
    
      consteval auto enumerators_of(info type_enum) -> vector<info>;
    }
    

    All return a std::vector. Reflection offers many metafunctions for queering type information and generating code.

    Enum <= String

    Applying the reverse steps makes an enum out of string.

    template <typename E>
      requires std::is_enum_v<E>                           
    constexpr std::optional<E> string_to_enum(std::string_view name) {
      template for (constexpr auto e : std::meta::enumerators_of(^E)) {
        if (name == std::meta::identifier_of(e)) {                     
          return [:e:];
        }
      }
    
      return std::nullopt;
    }
    

    What’s next?

    Reflection offers many metafunctions. I will apply them in my next post.

    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,