Egor Demidov

How to iterate over types without instantiation

A tutorial on how to iterate over a list of types in C++ and access their static fields without instantiating the types.

Thumbnail artwork

Sometimes it may be necessary to iterate over static fields of different types. Such an iteration may occur before some or all of the types have been instantiated. In that case, instantiation just for the purpose of accessing a static field is wasteful. Maybe, only one of the types will need to be instantiated in the program based on the contents of their static fields. Here, I show how to iterate over a typelist and access their static fields without instantiation by taking advantage of C++ templates.

Consider three structures A, B, and C:

struct A {
    A() = delete;
    static constexpr int val = 1;
};

struct B {
    B() = delete;
    static constexpr int val = 2;
};

struct C {
    C() = delete;
    static constexpr int val = 3;
};

None of them can be instantiated because their constructors are explicitly deleted. Our goal is to iterate over a list of types A, B, and C, and read the static filed val in each type. In order to do that, we first need to define a functor that will accept a type as a template argument, access the static field val of that type, and do something with the accessed value. In this tutorial, we will simply print val to standard output. Here is the functor:

template<typename T>
struct val_printer_functor {
    static void apply() {
        std::cout << T::val << std::endl;
    }
};

The functor contains a static function apply(), which will be invoked during the iteration. If you are using C++ 23, where static operator overloads are supported, feel free to overload operator () instead of creating a named function.

Now, to iterate over types, we overload two function templates:

template<template<typename T> typename functor, typename Head>
void iterate_types() {
    functor<Head>::apply();
}

template<template<typename T> typename functor, typename Head, typename Mid, typename... Tail>
void iterate_types() {
    functor<Head>::apply();
    iterate_types<functor, Mid, Tail...>();
}

The iterate_types() function needs to be called with the functor followed by one or more operand types in the template list. If there is more than one operand type, the second version of iterate_types() will be called. It will apply the functor to the first operand type in the template argument list and recursively call iterate_types() on the remaining types. When there is only one operand type remaining, the first variant is iterate_types() will be called, which will also apply the functor to the type and break recursion.

To put our implementation to a test, try:

int main() {
    iterate_types<val_printer_functor, A, B, C>();
    return 0;
}

The output is:

1
2
3

I hope you find this approach useful and thank you for reading!

Copyright © 2024 Egor Demidov. Content on this website is made available under CC BY 4.0 licence unless specified otherwise