Reflection in C++

Reflection in C++



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.

?



Modernes C++ Mentoring

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.

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

Rainer Grimm的更多文章

社区洞察

其他会员也浏览了