Magic Enum : la révolution des enums en C++

Aujourd'hui, il faut que je vous parler d'une bibliothèque qui a vraiment changé ma vie de développeur C++ au cours de la dernière année : Magic Enum. Elle est la création de Daniil Goncharov et est disponible sur Github sous licence MIT.

C'est une "header-only C++17 library" qui permet de faire la réflexion statique sur des enums.

Un bon exemple vaut mieux qu'un long discours :

#include <iostream>
#include "magic_enum.hpp"

enum class MyEnum {
    HELLO = 42, WORLD = 99
};

int main() {
    constexpr auto enumName = magic_enum::enum_type_name<MyEnum>();
    constexpr auto scoped = magic_enum::is_scoped_enum<MyEnum>::value;
    std::cout << enumName << " is scoped = " << std::boolalpha << scoped << '\n';

    constexpr auto count = magic_enum::enum_count<MyEnum>();
    std::cout << "It has " << count << " possible values :" << '\n';

    for (auto name : magic_enum::enum_names<MyEnum>()) {
        std::cout << "- " << name << '\n';
    }

    std::cout << '\n';

    for (unsigned int i = 0; i < count; ++i) {
        auto value = magic_enum::enum_value<MyEnum>(i);

        using namespace magic_enum::ostream_operators; // to pretty print the enum value
        std::cout << "Value = " << value << '\n';

        std::cout << "Integer = " << magic_enum::enum_integer(value) << '\n';
        std::cout << "Offset = " << magic_enum::enum_index(value).value() << '\n';

        auto name = magic_enum::enum_name(value);
        std::cout << "Name = " << name << '\n';

        std::cout << "---------" << '\n';
    }

    std::cout << '\n';    
    std::cout << "Cast to enum:" << '\n';

    using namespace magic_enum::ostream_operators; // to print std::optional<MyEnum>

    constexpr auto invalid = magic_enum::enum_cast<MyEnum>(0);
    constexpr  auto HELLO = magic_enum::enum_cast<MyEnum>("HELLO");
    constexpr auto hello = magic_enum::enum_cast<MyEnum>("hello");
    constexpr auto WORLD = magic_enum::enum_cast<MyEnum>("WORLD");
    constexpr auto WORLD_AGAIN = magic_enum::enum_cast<MyEnum>(99);

    std::cout << invalid << '\n';
    std::cout << HELLO << '\n';
    std::cout << hello << '\n';
    std::cout << WORLD << '\n';
}

Sortie :

MyEnum is scoped = true
It has 2 possible values :
- HELLO
- WORLD

Value = HELLO
Integer = 42
Offset = 0
Name = HELLO
---------
Value = WORLD
Integer = 99
Offset = 1
Name = WORLD
---------

Cast to enum:

HELLO

WORLD
WORLD

J'ai essayé de mettre autant que constexpr pour vous montrer qu'une bonne partie est faisable à la compilation, ce qui veut dire un coût nul à l'exécution. Comme j'ai une boucle, je ne peux pas mettre des constexpr partout mais si je choisis de prendre une valeur particulière de l'énumération, c'est possible :

int main() {
    using namespace magic_enum::ostream_operators;

    constexpr auto value = magic_enum::enum_cast<MyEnum>("HELLO").value();

    constexpr auto integer = magic_enum::enum_integer(value);
    constexpr auto index = magic_enum::enum_index(value).value();
    constexpr auto name = magic_enum::enum_name(value);

    std::cout << "Value = " << value << '\n';
    std::cout << "Integer = " << integer << '\n';
    std::cout << "Offset = " << index << '\n';
    std::cout << "Name = " << name << '\n';
}

Sortie :

Value = HELLO
Integer = 42
Offset = 0
Name = HELLO

Un monde de trucs cools s'ouvre à nous :

  • Fini les gens qui rajoutent une dernière valeur COUNT à l'énumération : à la place, il suffit d'utiliser enum_count().
  • Bonjour les affichages lisibles de valeurs avec "HELLO" à la place de "0" grâce à enum_name() ou l'opérateur de stream fourni.
  • A nous la lecture des chaines pour les transformer en valeur de l'union avec enum_cast().
  • Coucou la réponse facile à "cet entier est-il dans mon énumération ?" avec enum_cast().
  • Enfin la possibilité de donner des valeurs numériques arbitraires aux valeurs de l'énumération tout en les utilisant comme index de tableaux grâce a enum_index().

Si votre compilateur est compatible C++17, téléchargez, utilisez, appréciez. Sinon... il est temps de faire du lobbying pour mettre à jour votre compilateur !

Pierre

Que la vie de Pierre, expert embarqué Younup, serait terne sans les variadic templates et les fold expressions de C++17. Heureusement pour lui, Python a tué l'éternel débat sur l’emplacement de l’accolade : "alors, en fin de la ligne courante ou en début de la ligne suivante ?"

Homme de terrain, il est aussi à l’aise au guidon de son VTT à sillonner les chemins de forêt, dans une salle de concert de black metal ou les mains dans les soudures de sa carte électronique quand il doit déboguer du code (bon ça, il aime moins quand même !)

Son vœu pieux ? Il hésite encore... Faire disparaitre le C embarqué au profit du C++ embarqué ? Ou stopper la génération sans fin d'entropie de son bureau ?

Retours aux publications