Operator overload for enumerations
by Baduit
Article::Article
You probably all have seen a snippet of C++ code looking like this if you ever had a course or seen a tutorial about operator overload in C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
struct Person
{
std::string first_name;
std::string last_name;
};
std::ostream& operator<<(std::ostream& os, const Person& p)
{
return (os << "First name: " << p.first_name << " Last name: " << p.last_name);
}
int main()
{
Person p{ "Jane", "Doe" };
std::cout << p << std::endl;
// The output is: "First name: Jane Last name: Doe"
}
You can note that here the overload of the operator <<
is a free function and not a member function.
The point?
If it is a possible to do it with free functions, maybe it could also work for other user defined types like enumerations.
Let’s see what cppreference (the reference if you have to look up things about C++, yes, I know it is in the name):
When an operator appears in an expression, and at least one of its operands has a class type or an enumeration type, then overload resolution is used to determine the user-defined function to be called
My intuition was correct and some of you probably already knew it.
Some examples
The classic
You want to log your enum but you don’t use a library like Magic Enum (try it, it is awesome) then you write this type of stuff:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <utility>
enum class ToggleStatus
{
ON,
OFF
};
std::ostream& operator<<(std::ostream& os, ToggleStatus status)
{
if (status == ToggleStatus::ON)
os << "ON";
else
os << "FALSE";
return os;
}
The chaotic evil
Are you tired of playing a horny bard in D&D? Let’s play a chaotic evil developer and watch the world burn!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
enum class Int
{
ONE, // 0
TWO, // 1
THREE // 2
};
constexpr Int operator+(const Int& a, const Int& b) {
if (a == Int::ONE)
return Int::ONE;
else if (b == Int::ONE)
return Int::TWO;
else if (a == b)
return Int::THREE;
else
return Int::ONE;
}
static_assert((Int::ONE + Int::TWO) == Int::ONE);
static_assert((Int::TWO + Int::ONE) == Int::TWO);
static_assert((Int::TWO + Int::TWO) == Int::THREE);
static_assert((Int::THREE + Int::THREE) == Int::THREE);
static_assert((Int::TWO + Int::THREE) == Int::ONE);
The weird but funny
If you are still not using Magic Enum but you want to iterate over the values of an enumeration, here’s an example of how to do it by using your enumeration like an iterator:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <iostream>
#include <utility>
enum class UserStoryStatus
{
TO_REFINE, // 0
REFINED, // 1
TO_DO, // 2
WORK_IN_PROGRESS, // 3
TO_VALIDE, // 4
DONE, // 5
// Using out of range value as a sentinel would be UB
// Therefore, I need to define it
END
};
// Pre increment operator
// Note: this function could be a one liner with 0 copy, but I detailed it for learning purpose
UserStoryStatus& operator++(UserStoryStatus& uss)
{
// Use std::to_underlying to get the actual integer representation
auto i = std::to_underlying(uss);
// All values are continuous because we did not specify them
// It means we can just increase by one the integer representation of the enum
i += 1;
// The end should be checked using the UserStoryStatus::END sentinel value
// So I wont't check we are still in the right range
// Because this should be done with the end function
// But feel free to throw and exception if you want to do that check
// Or keep returning UserStoryStatus::END
uss = static_cast<UserStoryStatus>(i);
return uss;
}
// Post increment operator
UserStoryStatus operator++(UserStoryStatus& uss, int)
{
return ++uss;
}
// Dummy dereference operator, because iterator are supposed to be dereferenceable
UserStoryStatus operator*(UserStoryStatus uss)
{
return uss;
}
// Dummy type to use a the range
struct UserStoryStatusRange{};
// Begin and end function so it act as a range
UserStoryStatus begin(UserStoryStatusRange status)
{
return UserStoryStatus::TO_REFINE;
}
UserStoryStatus end(UserStoryStatusRange status)
{
return UserStoryStatus::END;
}
int main(int, char **)
{
UserStoryStatusRange range;
for (auto s: range)
std::cout << std::to_underlying(s) << std::endl;
}
Article::~Article
I hope you enjoyed this article or even better you learned something. With a little bit of (bad)luck it created a vocation for someone to be a chaotic evil developer.
Sources
- https://en.cppreference.com/w/cpp/language/operators