Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C++  Module 18 – C++26 previews: reflection, contracts and safety

C++26 reflection basics: ^^ and splicing

Reflect types, members and enumerators with the C++26 ^^ operator, query them with std::meta functions and splice them back into code with [: :].

  • Advanced
  • 20 minutes
  • Examples run with Clang 21.0.0, GCC 16.2.0 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 7 of 20 minutes free
Free: Every use of reflection reflects, queries and splices · A first program finds an enumerator's name from its value · The same pattern lists a struct's members · Practise: reflect, query and splice. With a pass: 2 in-depth parts and 3 interview answers, about 13 minutes.

What you will learn

  • Reflect types, members and enumerators with the ^^ operator
  • Query reflections with std::meta functions such as identifier_of and enumerators_of
  • Use splices, [: :], to turn reflections back into code
  • Explain why std::meta::info values exist only while a program compiles

Before you start

Before C++26, printing the four members of an Order struct meant writing every name a second time by hand, and the copy went stale the day someone added a member. Reflection lets a C++26 program examine its own declarations while it compiles. The operator ^^ turns a declaration, such as a type, a data member, an enumerator, a function or a namespace, into a value of type std::meta::info. Functions in the namespace std::meta answer questions about that value: its name, its type, its members. A splice, written [: r :], turns a reflection back into the code it stands for. All of this happens at compile time: a std::meta::info value never exists in the running program, which receives only the results, such as names and numbers. With GCC 16 you need -std=c++26 -freflection; Apple clang 21 does not implement reflection yet.

Preview: GCC 16 only

Status: in the final C++26 draft. All examples on this page ran on GCC 16.2 with -std=c++26 -freflection. Apple clang 21 cannot build them; the next lesson adds a portable fallback.

Every use of reflection reflects, queries and splices

Follow enum_name(Colour::green) from the source down to the running program:

Reflect with ^^, query with std::meta, splice with [: :], all while compiling; the running program keeps only plain values.1. Your codeenum class Colour { red, green, blue };While the program compiles5. When the program runsonly ordinary values are left:the comparisons and the string "green"2. Reflect with ^^^^Colour is a std::meta::info value3. Query with std::metaenumerators_of(^^Colour) givesthree reflections: red, green, blue4. Splice with [: e :] and read names[:e:] is Colour::green again;identifier_of(e) is "green"^^compiled intothe program

Reflection happens while the program compiles; the program keeps only its results

Text description of the diagram

The diagram shows five steps from top to bottom.

  1. Your code declares enum class Colour with the enumerators red, green and blue.
  2. An arrow labelled ^^ leads into a box titled "While the program compiles". Its first step: ^^Colour is a value of type std::meta::info that describes the enum.
  3. Next, a query: enumerators_of(^^Colour) gives three reflections, one each for red, green and blue.
  4. Last inside the box, a splice and a name: [:e:] turns a reflection back into Colour::green, and identifier_of(e) gives the string "green".
  5. An arrow labelled "compiled into the program" leads out of the box to a double-bordered box: when the program runs, only ordinary values are left, the comparisons and the string "green". No std::meta::info value exists at run time.

Every use of reflection follows the same three moves. You reflect a declaration with ^^. You query the reflection with std::meta functions, which are consteval, so they run only inside the compiler: enumerators_of lists an enum’s enumerators, nonstatic_data_members_of a class’s data members, identifier_of gives a name as written in the source, and type_of gives the type of a member. Then you splice results back into code with [: :], or keep plain values such as strings, which do survive into the running program.

A reflection is an ordinary value in every other way. You can store it in a constexpr variable, pass it to a consteval function, compare two of them with == and keep many in a vector. Because the work is finished before the program exists, the code it produces is the code you would have written by hand, and it costs nothing extra when the program runs.

Two more pieces make loops over query results possible. A query returns a std::vector of reflections, which lives only during constant evaluation: The compiler running code while it compiles, to find the value of a constant expression: the initialiser of a constexpr variable, a static_assert condition, an array bound. Only the result goes into the program, and the compiler caps how long it may run. More in “Compile-time programming with constexpr”, so std::define_static_array copies it into an array with static storage duration: The lifetime of a global, a variable at namespace scope or a static variable: it exists for the whole run of the program. Variables with thread storage duration, declared thread_local, exist once per thread instead. More in “C++ const, constexpr, consteval and constinit”. An expansion statement, template for, then repeats its body once for each element, with each element available as a constant.

Checkpoint

Check: Reflect, query, splice

  1. Question 1 of 2 What is left of the reflection work in enum_name.cpp when the program runs?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Only ordinary values: the comparisons and the names as strings

    Reflection is finished before the program exists. The code it produces is the code you would have written by hand, three comparisons and three names, and it costs nothing extra when the program runs.

  2. Question 2 of 2 Why does enum_name pass the result of enumerators_of through std::define_static_array before looping?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: enumerators_of returns a std::vector that exists only during constant evaluation, and the loop needs an array with static storage

    Query functions return std::vector<std::meta::info>, whose memory is released when the constant evaluation that created it ends. define_static_array copies the elements into an array that lives for the whole program, which template for can then walk, one element at a time.

A first program finds an enumerator’s name from its value

enum_name.cpp C++ · enum_name.cpp
// The name of an enumerator, from its value, with no table to keep in step.
#include <meta>
#include <print>
#include <string_view>

enum class Colour { red, green, blue };

template <typename E>
constexpr std::string_view enum_name(E value) {
    // ^^E reflects the enum; enumerators_of lists its enumerators while the program compiles.
    template for (constexpr std::meta::info e : std::define_static_array(std::meta::enumerators_of(^^E))) {
        if (value == [:e:]) {                  // [:e:] splices the enumerator back in: Colour::red, ...
            return std::meta::identifier_of(e);  // its name, as written in the source
        }
    }
    return "<unnamed>";
}

int main() {
    std::println("{}", enum_name(Colour::green));
    std::println("{}", enum_name(Colour::blue));
    std::println("{}", enum_name(static_cast<Colour>(7)));  // a value with no enumerator
    std::println("{}", std::meta::display_string_of(^^Colour));
}
Built and run with GCC 16.2 Python · enum_session.py
from session import run

# enum_name.cpp with GCC 16: reflection needs -std=c++26 and -freflection.
run("""
g++-16 -std=c++26 -freflection -Wall -Wextra enum_name.cpp -o enum_name && ./enum_name
""")

Output

$ g++-16 -std=c++26 -freflection -Wall -Wextra enum_name.cpp -o enum_name && ./enum_name
green
blue
<unnamed>
Colour

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 enum_session.py

Step through what the compiler makes of enum_name(Colour::green), then the run:

Interactive · stage by stage How enum_name(Colour::green) becomes three comparisons Step through the compiler's work on enum_name, stage by stage, then the program's run. The sub-labels say what each stage holds.
How enum_name(Colour::green) becomes three comparisons, step 8 of 8. Showing Reflect: ^^E, Query: enumerators_of, define_static_array, Expand: template for (copy 3: Colour::blue), The running program (returns "<unnamed>"). In focus: The running program.Reflect: ^^EE is ColourQuery: enumerators_ofred, green, bluedefine_static_arrayan array of 3, staticExpand: template forcopy 3: Colour::blueThe running programreturns "<unnamed>"

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. ^^E reflects the enum. main calls enum_name(Colour::green), so the compiler instantiates the template with E = Colour. ^^E turns that type into a std::meta::info value, a handle that exists only inside the compiler.
  2. The query lists 3 names. enumerators_of(^^E) is consteval, so it runs inside the compiler. It returns a std::vector of three reflections, one each for red, green and blue, in the order they were declared.
  3. A static copy. That vector is freed when the constant evaluation that made it ends. define_static_array copies the three reflections into an array with static storage, which template for can walk.
  4. Copy 1: red. template for stamps out one copy of its body for each element. In the first copy e reflects red, so [:e:] is Colour::red and the test reads value == Colour::red.
  5. Copy 2: green. In the second copy [:e:] is Colour::green, and identifier_of(e) gives "green", the name exactly as written in the source, as a std::string_view.
  6. Copy 3, then the end. The third copy tests Colour::blue. After the copies comes return "<unnamed>", for a value that no enumerator has. The compiler is done, and no reflection is left.
  7. The program runs. At run time enum_name(Colour::green) is three ordinary comparisons. The first fails, the second matches, and the function returns "green", the first line the run printed.
  8. A value with no name. enum_name(static_cast<Colour>(7)) fails all three comparisons and reaches the last return, so the third line of the run is <unnamed>: the case a hand-written switch tends to forget.

enum_name reflects whatever enum it is given, ^^E, and loops over its enumerators. For each one, [:e:] splices the enumerator back in, so the comparison is value == Colour::red, then value == Colour::green, and so on. The first match returns identifier_of(e), the name exactly as written. A value that names no enumerator, such as static_cast<Colour>(7), falls through to "<unnamed>", which is the case a hand-written switch usually forgets. The last line shows display_string_of, a second way to get a name: it is meant for people, and its wording is up to the compiler.

Why template for and not an ordinary for loop? A splice needs a constant reflection, but the variable of an ordinary loop changes while the program runs. An expansion statement instead stamps out one copy of its body for each enumerator, as if you had written three if statements by hand, and in each copy e is a constant. The function works for any enum without change, and adding an enumerator needs no edit anywhere else: there is no table of names to keep in step with the enum.

Checkpoint

Check: An enumerator's name from its value

  1. Question 1 of 2 What does enum_name(static_cast<Colour>(7)) return?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: "<unnamed>", because no enumerator has the value 7

    The loop compares the value with each enumerator in turn and finds no match, so the function reaches its final return. That is the case a hand-written switch tends to forget.

  2. Question 2 of 2 You add yellow to enum class Colour. What else must change for enum_name(Colour::yellow) to return "yellow"?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Nothing; the loop reads the enumerators from the enum itself

    enum_name works for any enum without change, and adding an enumerator needs no edit anywhere else: there is no table of names to keep in step with the enum.

The same pattern lists a struct’s members

The same pattern lists the data members of a class, and splicing a member reflection into object.[:m:] reads that member:

members.cpp C++ · members.cpp
// Every data member of a struct, with its name and value, from one generic function.
#include <meta>
#include <print>
#include <string>

struct Order {
    int id;
    std::string customer;
    double amount_rupees;
    bool paid;
};

template <typename T>
void print_members(const T& object) {
    constexpr auto ctx = std::meta::access_context::unchecked();  // all members, whatever their access
    template for (constexpr std::meta::info m : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, ctx))) {
        std::println("{} = {}", std::meta::identifier_of(m), object.[:m:]);  // object.[:m:] is object.id, ...
    }
}

int main() {
    print_members(Order{1042, "Asha", 1299.5, true});
}
Four members, four lines Python · members_session.py
from session import run

run("""
g++-16 -std=c++26 -freflection -Wall -Wextra members.cpp -o members && ./members
""")

Output

$ g++-16 -std=c++26 -freflection -Wall -Wextra members.cpp -o members && ./members
id = 1042
customer = Asha
amount_rupees = 1299.5
paid = true

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 members_session.py

Each copy splices one member, with its own type:

Copy object.[:m:] becomes Printed
1 object.id, an int id = 1042
2 object.customer, a std::string customer = Asha
3 object.amount_rupees, a double amount_rupees = 1299.5
4 object.paid, a bool paid = true

nonstatic_data_members_of needs an access context, which decides whether private members are included. access_context::unchecked() returns all of them; access_context::current() returns only the members that the code at that point may access, as a normal member access would. Each object.[:m:] becomes object.id, object.customer and so on, with its real type, so std::println formats the int, the std::string, the double and the bool correctly without any casts.

The members arrive in declaration order, the order of the struct’s layout. A member whose type std::println cannot format, such as a nested struct, stops the build with a formatter error; the next lesson handles nesting by calling the same function again for such members.

Checkpoint

Check: Members read through splices

  1. Question 1 of 2 print_members asks for members with access_context::unchecked(). The struct it is given has a private member. Is that member printed?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Yes; unchecked() returns every member, whatever its access

    access_context::unchecked() returns all the members; access_context::current() returns only the members that the code at that point may access, as a normal member access would.

  2. Question 2 of 2 In print_members, m reflects Order::customer. What does this line print for the order of members.cpp?

    Read the code, then choose one answer.

    std::println("{} = {}", std::meta::identifier_of(m), object.[:m:]);
    Show the answer: Question 2 of 2

    Answer: customer = Asha

    identifier_of gives the name as written, customer, and object.[:m:] is spliced into object.customer, a std::string that println formats as Asha. No cast is needed, because the splice has the member's real type.

Practise: reflect, query and splice

Ten questions on the three ideas above follow, and the idea you have answered worst so far comes first.

Practice that adapts to you

Practise this lesson

10 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 10 What does the expression ^^Colour produce?

    Choose one answer.

    Show the answer: Question 1 of 10

    Answer: A std::meta::info value that describes the enum, usable only while the program compiles

    ^^ reflects a declaration. The result has type std::meta::info and exists only during compilation; names and other plain values computed from it are what reach the running program.

  2. Question 2 of 10 Compared with a hand-written chain of if statements, what does enum_name cost when the program runs?

    Choose one answer.

    Show the answer: Question 2 of 10

    Answer: Nothing extra; the code it produces is the code you would have written by hand

    Because reflection runs while compiling, enum_name ends up as three comparisons and three names, the same program you would get from a switch written by hand.

  3. Question 3 of 10 Which of these can you do with a std::meta::info value?

    Choose every answer that is right.

    Show the answer: Question 3 of 10

    Answer:

    • Store it in a constexpr variable
    • Pass it to a consteval function
    • Compare two of them with ==

    You can store reflections in constexpr variables, pass them to consteval functions, compare them and keep many in a vector, all while compiling. Only the results, such as names and numbers, reach the program.

  4. Question 4 of 10 The run's last line printed Colour for std::meta::display_string_of(^^Colour). Who decides the wording of that function's text?

    Choose one answer.

    Show the answer: Question 4 of 10

    Answer: The compiler; display_string_of is meant for people, and its wording is up to each compiler

    identifier_of gives a name exactly as written in the source; display_string_of gives a name for people, worded by the compiler, so it suits messages rather than anything stored or compared.

  5. Question 5 of 10 A colleague's enum class Level { low, high }; needs names too. What must change in enum_name?

    Choose one answer.

    Show the answer: Question 5 of 10

    Answer: Nothing; enum_name<Level> reflects whatever enum it is given

    enum_name reflects ^^E, the enum it is instantiated for, and loops over that enum's enumerators. Any enum works without change.

  6. Question 6 of 10 Why can enum_name not loop over the reflections with an ordinary for loop?

    Choose one answer.

    Show the answer: Question 6 of 10

    Answer: A splice needs a constant reflection, and an ordinary loop's variable changes while the program runs

    An expansion statement makes one copy of its body for each element, as if you had written three if statements by hand, and in each copy e is a constant that [:e:] can splice.

  7. Question 7 of 10 In which order does nonstatic_data_members_of return a struct's members?

    Choose one answer.

    Show the answer: Question 7 of 10

    Answer: In declaration order, the order of the struct's layout

    The members arrive in declaration order, the order of the struct's layout, so generated code is stable from one build to the next.

  8. Question 8 of 10 Which access context returns only the members that the code at that point may access?

    Choose one answer.

    Show the answer: Question 8 of 10

    Answer: access_context::current()

    current() returns only the members the code at that point may access, as a normal member access would; unchecked() returns all of them.

  9. Question 9 of 10 Order gains a member Address shipping;, a struct that std::println has no formatter for. What happens when print_members is compiled for Order?

    Choose one answer.

    Show the answer: Question 9 of 10

    Answer: The build stops with a formatter error at the copy of the body for shipping

    A member whose type std::println cannot format, such as a nested struct, stops the build with a formatter error. The next lesson handles nesting by calling the same function again for such members.

  10. Question 10 of 10 Why does print_members format amount_rupees as 1299.5 and paid as true without any casts?

    Choose one answer.

    Show the answer: Question 10 of 10

    Answer: Each object.[:m:] is spliced into the member itself, with its real type

    Each copy of the loop body splices one member: object.id, object.customer and so on, each with its real type, so std::println formats the int, the std::string, the double and the bool correctly.

The std::meta queries and the four splice forms, each one run Pro: opens with a Pro, Premium or Ultimate pass

A handful of queries answer most questions about a type, and a splice can stand in four places in code. This part runs each of them on GCC 16.2 and shows where the compilers’ own wording leaks into the results.

In this part:

  • What std::meta reports for one struct, with the numbers
  • When a reflection has no name: an error reproduced and two fixes
  • Four kinds of splice, and where typename is required
  • What current(), unprivileged() and unchecked() let a query see

10 runnable examples (C++, Python) · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Why reflections never reach run time: the errors reproduced, and an exercise Pro: opens with a Pro, Premium or Ultimate pass

Every reflection value has to be used up while the program compiles. Six short programs show the errors GCC gives when that rule is broken, how to fix them, and how reflections compare.

In this part:

  • A reflection chosen at run time, and the fix
  • How reflections compare: aliases are entities of their own
  • Your turn: member_count and describe

11 runnable examples (C header, C++, Python) · about 6 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Interview questions

  1. Fresher to mid · Warm-up

    Why can a std::meta::info value not be used when the program runs?

    std::meta::info is a consteval-only type: its values describe declarations inside the compiler, so they exist only while the program compiles. An object of that type may be a constexpr variable, a template argument or a value inside one constant evaluation, and the std::meta query functions are consteval. Code that needs reflection results at run time must turn them into ordinary values first, such as the string_view that identifier_of returns, a count, or code produced by a splice. That is also why the loop over members is an expansion statement, template for, which repeats code while compiling, instead of an ordinary for loop.

  2. Mid to senior

    A generic logger prints every data member of any struct by reflection, using nonstatic_data_members_of with access_context::unchecked(). A security audit flags it before release. What is the risk, and how do you change the logger and its tests?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    A serialiser writes each object's type name, taken from display_string_of, into saved files, and checks it again when loading. All tests pass with GCC 16. What can go wrong in the field, and what would you store instead?

    Model answer, Pro. First step and follow-ups

  4. Senior

    A unit test checks that a serialiser covers every field: it compares the number of fields written with nonstatic_data_members_of(^^T, unchecked()).size(). For a class that derives from another, the test passes while a field is missing from the output. Why, and how do you fix the test?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • ^^ reflects a declaration as a std::meta::info value; std::meta functions query it; [: :] splices it back into code.
  • Query results are vectors of reflections: std::define_static_array turns them into a static array, and template for repeats code once for each element.
  • identifier_of gives names as written; display_string_of gives names for people, in the compiler’s own words.
  • access_context::unchecked() sees private members, current() only what the calling code may access.
  • GCC 16 needs -std=c++26 -freflection; Apple clang 21 has no reflection.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.