It is my ambition to explore C++26 in bits and pieces. Hopefully, by the end of this year, I will be able to explore all the major aspects of it and be in a position to evaluate which of these features to use and promote and which not to use. However, at this point, it is important to understand each and every aspect in simple terms, keeping all the clutter aside.

Last week I explored and wrote about template for in C++26. My today’s goal is to have a first look at C++26 reflection. To start the discussion let’s take a look at the following enum class:

enum class Colour
{
Red,
Green,
Blue
};

Until C++26, there were no easy means to peek into an enum class and find out what enumerators it contained. So, if you wanted to convert the enumerators to strings, you would end up doing something manual like the following:

std::string_view to_string(Colour colour) {
switch (colour) {
case Colour::Red: return "Red";
case Colour::Green: return "Green";
case Colour::Blue: return "Blue";
}
return "<unknown>";
}

There are multiple issues here. To start with, if you update the enum class by adding another colour (a new enumerator), you also have to update the to_string() function. Furthermore, there is no easy way to check whether a particular enumerator exists in the enum.

C++26 provides an enumerators_of() function, which is part of the std::meta namespace and returns you a collection of reflections of the enumerators in the same order as you have declared. So a call like the following:

std::meta::enumerators_of(^^E)

will return something like this:

reflection of Colour::Red
reflection of Colour::Green
reflection of Colour::Blue

E stands for the enum type, which is Colour in this example.

Note that std::meta::enumerators_of(^^E) effectively gives a compile time vector of type std::meta::info. std::meta::info represents an enumerator. You can pass it to another reflection function to retrieve more information. For example to extract its name, you can pass the meta infor to std::meta::identifier_of() function etc.

A diagram view of the discussion so far will be like this:

How C++26 reflection discovers enum members and extracts their names and values at compile time

So with that in place we can now iterate over the enum type as thr following:

template <typename E>
requires std::is_enum_v<E>
constexpr std::string_view enum_to_string(E value)
{
template for (constexpr auto e : std::define_static_array(std::meta::enumerators_of(^^E)))
{
if (value == [:e:])
{
return std::meta::identifier_of(e);
}
}
return "<unknown>";
}

I have explained the use of template for in the previous post. The std::define_static_array is another C++26 feature. In short it takes a range and coverts it its elements into an array with static storage duration. We need to do that so template for can operate on it. For more explanation please look into my previous post on template for.

One of things in this code which is not self explanatory is the expression [:e:]. In simple terms, it represents the reflected enumerator as an actual C++ enumerator value.

Full Listing

A possible full C++26 program using the above code snippets may look like the following:

#include <iostream>
#include <meta>
#include <string_view>
#include <type_traits>
template <typename E>
requires std::is_enum_v<E>
constexpr std::string_view enum_to_string(E value)
{
template for (constexpr auto e : std::define_static_array(std::meta::enumerators_of(^^E)))
{
if (value == [:e:])
{
return std::meta::identifier_of(e);
}
}
return "<unknown>";
}
enum class Colour
{
Red,
Green,
Blue,
Yellow
};
int main()
{
static_assert(enum_to_string(Colour::Green) == "Green");
constexpr std::string_view enum_name = std::meta::identifier_of(^^Colour);
constexpr std::size_t enumerator_count = std::meta::enumerators_of(^^Colour).size();
std::cout << "Enum type: " << enum_name << "\n";
std::cout << "Number of enumerators: " << enumerator_count << "\n";
std::cout << "Enumerators:\n";
template for (constexpr auto e : std::define_static_array(std::meta::enumerators_of(^^Colour)))
{
constexpr std::string_view name = std::meta::identifier_of(e);
constexpr int value = static_cast<int>([:e:]);
std::cout << " Name: " << name << ", value: " << value << "\n";
}
std::cout << "Colour::Green converted to string: " << enum_to_string(Colour::Green) << "\n";
return 0;
}

Compile and Run

$ ./sample_reflection
Enum type: Colour
Number of enumerators: 4
Enumerators:
Name: Red, value: 0
Name: Green, value: 1
Name: Blue, value: 2
Name: Yellow, value: 3
Colour::Green converted to string: Green

Notice expressions such as std::meta::enumerators_of(^^Colour).size(), which give us the number of enumerators declared in the enum.

Also, using std::meta::identifier_of(^^Colour), we can obtain the type’s declared name programmatically.

NOTE: C++26 reflection is still experimental and hence needs the compilation flag -freflection for the code to be compiled. Also, I have dicussed how to install and setup your C++26 environment in one my previous posts Compile Your First C++26 Program with GCC 16.1.

This is my beginning of exploration on reflection, as I explore more I will continue sharing.

The sample code can be downloaded from here.


Discover more from Tech For Talk

Subscribe to get the latest posts sent to your email.

Leave a Reply