---
title: Synthesizing enum at compile time with define_enum
document: P4033R0
date: 2026-03-30
audience: SG7 Reflection
reply-to:
  - "Aurelien Cassagnes"
paper-type: proposal
---

---

## Motivation

The generative capability of reflection as they were introduced in C++26 are limited to `define_aggregate`, with no quick paths to more powerful facilities (See [p3294r2] for example). We can already leverage `define_aggregate` to impressive effects (See a JSON parser [here](https://brevzin.github.io/c++/2025/06/26/json-reflection/)), here we introduce another basic and lightweight building block: `define_enum`.

**enum-based variant** Using `define_enum()` we can simulate some enum-based discriminant `variant`

```
template<class... Ts>
struct named_variant_T {
  std::variant<Ts...> v;

  enum class kind : std::size_t;
  consteval {
      std::vector<std::meta::info> enumerators;
      for (auto type : {^^Ts...}) {
          enumerators.push_back(enumerator_spec({
            .name = std::define_static_string(identifier_of(type))
          }));
      }
      enumerators.push_back(enumerator_spec({.name = "Default"}));
      define_enum(^^kind, enumerators);
  }

  constexpr kind which() const noexcept {
    auto i = v.index();
    if (i == std::variant_npos) return kind::Default;
    return static_cast<kind>(i);
  }
  // ...
};

using Message = named_variant_T<X, Y, Z>;
```

This unlocks a `variant` that has some favorable properties Looking at a simple example of non-`visit()` based access

| Before
      After
     

using VarPet = std::variant<Dog, Tanuki, Cat>;
// To keep in sync
enum VariantPetIndex {
  Dog,
  Tanuki,
  Cat,
};
//...
VarPet animal(Dog{});
// ...
switch (animal.index()) {
  default: break;
  case VariantPetIndex::Dogs break;
  case VariantPetIndex::Tanuki: break;
  case VariantPetIndex::Cats break;
}


using VarPet = named_variant_T<Dog, Tanuki, Cat>;
//...
VarPet animal(Dog{});

switch (animal.which()) {
  case VarPet::kind::Default: break;
  case VarPet::kind::Dog: break;
  case VarPet::kind::Tanuki: break;
  case VarPet::kind::Cats break;
} | After
     

using VarPet = std::variant<Dog, Tanuki, Cat>;
// To keep in sync
enum VariantPetIndex {
  Dog,
  Tanuki,
  Cat,
};
//...
VarPet animal(Dog{});
// ...
switch (animal.index()) {
  default: break;
  case VariantPetIndex::Dogs break;
  case VariantPetIndex::Tanuki: break;
  case VariantPetIndex::Cats break;
}


using VarPet = named_variant_T<Dog, Tanuki, Cat>;
//...
VarPet animal(Dog{});

switch (animal.which()) {
  case VarPet::kind::Default: break;
  case VarPet::kind::Dog: break;
  case VarPet::kind::Tanuki: break;
  case VarPet::kind::Cats break;
} | using VarPet = std::variant<Dog, Tanuki, Cat>;
// To keep in sync
enum VariantPetIndex {
  Dog,
  Tanuki,
  Cat,
};
//...
VarPet animal(Dog{});
// ...
switch (animal.index()) {
  default: break;
  case VariantPetIndex::Dogs break;
  case VariantPetIndex::Tanuki: break;
  case VariantPetIndex::Cats break;
}


using VarPet = named_variant_T<Dog, Tanuki, Cat>;
//...
VarPet animal(Dog{});

switch (animal.which()) {
  case VarPet::kind::Default: break;
  case VarPet::kind::Dog: break;
  case VarPet::kind::Tanuki: break;
  case VarPet::kind::Cats break;
} | using VarPet = named_variant_T<Dog, Tanuki, Cat>;
//...
VarPet animal(Dog{});

switch (animal.which()) {
  case VarPet::kind::Default: break;
  case VarPet::kind::Dog: break;
  case VarPet::kind::Tanuki: break;
  case VarPet::kind::Cats break;
} |
| --- | --- | --- | --- |
| using VarPet = std::variant<Dog, Tanuki, Cat>;
// To keep in sync
enum VariantPetIndex {
  Dog,
  Tanuki,
  Cat,
};
//...
VarPet animal(Dog{});
// ...
switch (animal.index()) {
  default: break;
  case VariantPetIndex::Dogs break;
  case VariantPetIndex::Tanuki: break;
  case VariantPetIndex::Cats break;
}


using VarPet = named_variant_T<Dog, Tanuki, Cat>;
//...
VarPet animal(Dog{});

switch (animal.which()) {
  case VarPet::kind::Default: break;
  case VarPet::kind::Dog: break;
  case VarPet::kind::Tanuki: break;
  case VarPet::kind::Cats break;
} | using VarPet = named_variant_T<Dog, Tanuki, Cat>;
//...
VarPet animal(Dog{});

switch (animal.which()) {
  case VarPet::kind::Default: break;
  case VarPet::kind::Dog: break;
  case VarPet::kind::Tanuki: break;
  case VarPet::kind::Cats break;
} |  |  |

If

variant<Dog, Tanuki, Cat>

is later augmented with

variant<Dog, Tanuki, Racoon, Cat>

, or the variant are shuffled, the index-based switch table will break silently, not the enum based one.
Now, to be fair,

visit()

does not suffer this limitation. However we should recognize that

1. It is considerably more verbose switch tables lend themselves easily to jump table dispatch (which is fast, which is nice)

See [this example](https://godbolt.org/z/5a3Yenz8d) where we also leverage annotations on enumerators.

## What about unscoped enum ?

Note that above, we made the explicit choice of targetting scoped enumeration only, and so the following is not valid

```
enum HealthySnacks: int;
consteval {
  define_enum(^^HealthySnacks, {
    enumerator_spec({.name = "Carrot"}),
    enumerator_spec({.name = "Celeri"}),
    enumerator_spec({.name = "BabaAuRhum"})
  });
}

// From here on out BabaAuRhum == 2
```

Our rationale behind this conservative approach is entirely rooted in our implementation experience (granted not extensive).
Synthesizing enumerators of a scoped enum is a fairly simple operation, on the other hand rewiring the proper context for enumerators of an unscoped enum is quite more troublesome and error prone...
Also having constants popping out of existing in a fairly large scope could be troublesome...
Hence, for now, we made the cautious choice of limiting the operation to scoped enum only.

## Feature

The actual feature proposed here is `define_enum()`, allowing to complete an opaque scoped enumeration alongside the description of its enumerators. In turn, `define_enum` relies on a lightweight description of the enumerators (enumerator options), that are passed to `enumerator_spec()`. All those pieces will be described here, ultimately it should feel familiar to any `define_aggregate()` enthusiast.

### enumerator_options

```
template<integral I>
struct enumerator_options {
  string       name;
  optional<I>  value       = {};
  vector<info> annotations = {};
  vector<info> attributes  = {};
};
```

As when defining manually an enumerator, the integral `value` is optional. If ommited it will be computed in the same fashion that is done already (incrementing previous value). Diverging with the original design of `define_aggregate()`, annotations and attributes are directly supported here via `.annotations` and `.attributes`. Finally note that the support for `.attributes` here relies entirely on the adoption of attributes reflection via [p3385r7].

### enumerator_spec

```
consteval info enumerator_spec(enumerator_options props);
```

enumerator_spec

returns the reflection of an enumerator description from the passed in properties.

### is_enumerator_spec

```
consteval bool is_enumerator_spec(info r);
```

is_enumerator_spec

returns

true

when

r

is a reflection returned by

enumerator_spec

. It is kept distinct from the reflection of an enumerator, since, while similar on some front they are used for distinct purposes.

### define_enum

```
template <reflection_range R = initializer_list<info>>
    consteval info define_enum(info targetEnum, R&& members);
```

Finally

define_enum

completes a scoped enumeration declaring a set of enumerators under it. We pass in the reflection of the opaque scoped enumeration we want to complete, and a sequence of reflection obtained via

enumerator_spec

.
As was the decision for

define_aggregate()

, when it comes to running compile time operations with side effect, we force

define_enum

to appear within a consteval block.
Only following the block, is the enumeration completed with its enumerators as specified.

## Wording

### Library

#### Meta synopsis [meta.syn]

Add after [meta.reflection.define.aggregate]

```
namespace std {
  // ... 


  // [meta.reflection.define.enum], enum definition generation

  template<integral I> struct enumerator_options;

  consteval info enumerator_spec(enumerator_options options);

  consteval bool is_enumerator_spec(info r);

  template <reflection_range R = initializer_list<info>>
    consteval info define_enum(info targetEnum, R&& members);
```

#### Reflection union definition generation [meta.reflection.define.enum]

```
namespace std::meta {
  template<integral I>
  struct enumerator_options {
    optional<string> name = std::nullopt;
    optional<I> value = {};
    vector<info> attributes = {};
    vector<info> annotations = {};
  };
}
consteval info enumerator_spec(enumerator_options options);
```

Returns:

A reflection of an enumerator description (N, V, AT, AN) where

- *N* is either the identifier held by `options.name` or ⊥ if `options.name` does not contain a value, *V* is either the value held by `options.value` or ⊥ if `options.value` does not contain a value, *AT* is a potentially empty sequence of attribute reflections from `options.attributes`, and *AN* is a potentially empty sequence of values `constant_of(r)` for each *r* in `options.annotations`

Throws:

meta::exception

unless the following conditions are met:

- `options.name.has_value()` is false, or `name.value()` is a valid identifier ([lex.name]) that is not a keyword ([lex.key]), and for each *r* in *options.attributes*, `is_attribute(options.attributes_of[r])` is true, and for each *r* in `options.annotations`, `type_of(r)` represents a non-array object type, and evaluation of `constant_of(r)` does not exit via an exception.

```
consteval bool is_enumerator_spec(info r);
```

Returns:

true

if

r

represents the reflection of an enumerator description. Otherwise,

false

```
template <reflection_range R = initializer_list<info>>
  consteval info define_enum(info targetEnum, R&& members);
```

Let

E

be the enum represented by

targetEnum

and

r<sub>i</sub>

be the

i<sup>th</sup>

reflection value in

members

.
    For every

r<sub>i</sub>

in

members

, let (

N<sub>i</sub>

,

V<sub>i</sub>

,

AT<sub>i</sub>

,

AN<sub>i</sub>

) be the corresponding enumerator description reflection.

*Constant when:*

- `is_enumerator_spec(ri)` is true, and `targetEnum` is a reflection that represents a scoped enumeration type, and `targetEnum` is an opaque enumeration from every point in the evaluation context, and for every pair (`i`, `j`) where `i` < `j` and *N<sub>i</sub>* is not ⊥ and *N<sub>j</sub>* is not ⊥, then either: *N<sub>i</sub>* is not the same identifier as *N<sub>j</sub>* or *N<sub>i</sub>* is the identifier `_` (U+005f low line).

*Effects:* Produces an injected declaration `D` ([expr.const]) that defines `E` and has properties as follows:

- The target scope of `D` is the scope to which `E` belongs ([basic.scope.scope]). The locus of `D` follows immediately after the core constant expression currently under evaluation. The injected definition has an *enumerator-list* with one *enumerator-definition* for each element of `members`, in order. For the *i<sup>th</sup>* element of `members`, the corresponding *enumerator-definition*: is preceded by the attributes denoted by the attribute reflections in *AT<sub>i</sub>*, and is preceded by an annotation whose underlying constant ([dcl.attr.annotation]) is r for every reflection r *AN<sub>i</sub>* has *enumerator-name* *N<sub>i</sub>*, and has an *enumerator-initializer* if and only if *V<sub>i</sub>* ≠ ⊥; if present, it is formed from *V<sub>i</sub>*. The values of enumerators for which *V<sub>i</sub>* = ⊥ are determined as specified in [dcl.enum] for enumerators without an explicit *enumerator-initializer*.

Returns:

targetEnum

.

## Status

This proposal was implemented (

[clang-implementation]

) on top of the Clang P2996 branch, and is available for test on the same Compiler Explorer branch.

## Acknowledgements

The original motivation for this facility was discussed by A. Jiang over

Github issues

. The early version of this proposal greatly benefitted from Matthias Wippich feedback and insights.

## References

### Normative References

**[CLANG-IMPLEMENTATION]
   Aurelien Cassagnes. [define_enum](https://github.com/bloomberg/clang-p2996/pull/263). URL: [https://github.com/bloomberg/clang-p2996/pull/263](https://github.com/bloomberg/clang-p2996/pull/263)
[P3294R2]
   Barry Revzin, Andrei Alexandrescu, Daveed Vandevoorde. [Code Injection with Token Sequences](https://wg21.link/p3294r2). 15 October 2024. URL: [https://wg21.link/p3294r2](https://wg21.link/p3294r2)
[P3385R7]
   Aurelien Cassagnes. [Attributes reflection](https://wg21.link/p3385r7). 16 February 2026. URL: [https://wg21.link/p3385r7](https://wg21.link/p3385r7)**
