c++for-looptemplatesrange-based-loopc++26

How does `template for` iteration work in C++26?


I'm experimenting with the new C++ compile-time reflection features (as described in P2996R0) and I testing a simple enum_to_string() utility using template for:

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

  return "<unnamed>";
}    

As I understand it, this is not a regular loop but rather a compile-time unrolling of repeated code with different template substitutions.

Is this mechanism compatible with the begin()/end() functions used by range-based for loops?

My concrete questions:

  1. What is the actual type returned by std::meta::enumerators_of(^^E)?

  2. What enables it to be iterated using template for? Does template for rely on begin()/end()?

  3. Are there any formal requirements for the right-hand side of a template for? Does it need to model a specific concept or structure?

  4. Can a user-defined function return something that's usable in a template for loop?

  5. Is there a way to define our own object that behaves like members_of or enumerators_of?


Solution

  • As I understand it, this is not a regular loop but rather a compile-time unrolling of repeated code with different template substitutions.

    That's correct. It stamps out the body of the loop once for each iteration.

    1. What is the actual type returned by std::meta::enumerators_of(^^E)?

    std::vector<std::meta::info>.

    1. What enables it to be iterated using template for? Does template for rely on begin()/end()?
    2. Are there any formal requirements for the right-hand side of a template for? Does it need to model a specific concept or structure?

    There are three forms to expansion statements:

    1. Expression lists: {e...}. This just is one iteration per expression.
    2. Ranges (which for the moment have to be random-acess ranges)
    3. Anything you can use structured bindings with

    in that order.

    I think the wording is actually quite clear (here so far, will link to the actual draft standard once it's merged in a few days), would recommend just seeing what it does.

    1. Can a user-defined function return something that's usable in a template for loop?
    2. Is there a way to define our own object that behaves like members_of or enumerators_of?

    Of course. Any random-access range or destructurable type works.


    Note also that the example as written doesn't work. This is because internally we create a static constexpr variable to hold the range, which we cannot do for a vector due to lack of non-transient constexpr allocation.

    Instead you'll have to first promote the vector to something that you can create a static constexpr variable of. We have a workaround for this called define_static_array, which takes the contents of an arbitrary range, promotes it to a static storage duration array, and returns a span to it. The full example would become:

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