short

Naming a constant makes GCC stop believing it is one

· english· audience: working-cpp· AI-generated, reviewed by Filip Sajdak

This compiles on GCC 16.1:

template for (constexpr auto m : std::define_static_array(
                  std::meta::nonstatic_data_members_of(^^T, ctx))) {
    std::println("  {} = {}", std::meta::identifier_of(m), obj.[: m :]);
}

This does not:

constexpr auto members = std::define_static_array(
    std::meta::nonstatic_data_members_of(^^T, ctx));

template for (constexpr auto m : members) {
    std::println("  {} = {}", std::meta::identifier_of(m), obj.[: m :]);
}
error: 'members' is not a constant

Same expression, same initializer, same constexpr. The only difference is that the second one has a name.

The matrix

Range given as GCC 16.1 clang-p2996
named constexpr local rejected accepted
identical expression inline accepted accepted

Clang takes both spellings and prints the same output either way. Naming a constant expression does not stop it being one.

Why it is worth knowing rather than filing away

The named form is the one people write. You compute the member list once, give it a name that says what it is, and use it. Splitting a long expression out of a loop header is ordinary style. Here it turns a working program into a compile error. The message sounds as if your code is wrong.

The workaround is unpleasant in proportion. Inline the expression back into the template for. A reviewer would ask you to undo exactly that edit.

If you are porting reflection code written against clang, expect this shape of breakage. GCC has the feature. It disagrees about when a constexpr variable is usable as an expansion range.

A note on the reproducer

The version in the examples repo switches between the two forms with a macro. The first draft was wrong in a way worth repeating:

#if FORM == named       // always true

Undefined identifiers evaluate to 0 in a preprocessor condition. So the test FORM == named is really 0 == 0 whichever way you set it, and both branches take the same path. It looked like GCC rejecting the workaround too. Instead of comparing a macro to a bare word, ask whether it is defined.


Sources: