Magic Enum : la révolution des énumérations en C++
Tags: cpp, programming, library
Published: 2021-04-29 09:24:36

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](https://github.com/Neargye/magic_enum) 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 :

```cpp
#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 :

```cpp
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 😅