Aujourd'hui, il faut que je vous parle 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'utiliserenum_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 😅