Enumerations are great however printing them out are quite tricky. For unscoped enums, they implicitly convert to an int. For scoped enums, the type safety is enforced. Fortunately, there is a solution!
This Nibble will demonstrate how to use operator overloading to print enumerations and to receive them as input.
Overloading operator<< to print an enumerator
Before we proceed, let’s quickly recap how operator<< works when used for output.
Consider a simple expression like std::cout << 5. std::cout has type std::ostream (which is a user-defined type in the standard library), and 5 is a literal of type int.
When this expression is evaluated, the compiler will look for an overloaded operator<< function that can handle arguments of type std::ostream and int. It will find such a function (also defined as part of the standard I/O library) and call it.
Let’s overload << to accept enumerators!
#include <iostream>
#include <string_view>
enum class CoffeeSize {
Small,
Medium,
Large
};
constexpr std::string_view toString(CoffeeSize size){
switch (size) {
case CoffeeSize::Small: return "small";
case CoffeeSize::Medium: return "medium";
case CoffeeSize::Large: return "large";
}
return "unknown";
}
std::ostream& operator<<(std::ostream& out, CoffeeSize size)
{
return out << toString(size);
}
int main(){
CoffeeSize size { CoffeeSize::Medium };
std::cout << "Selected size: " << size << '\n';
}
// Terminal output:
Selected size: medium
The overload takes two parameters and outsources the printing to toString().
The stream is passed by reference because streams are not copied.
This line is the key:
return out << toString(size);
The operator does not return a string. It writes the string into the stream, then returns the stream by reference.
Returning std::ostream& is what allows chaining:
std::cout << "Selected size: " << size << '\n'
*std::ostream can be of type std::cout, std::cer, basically any output stream*
Overloading operator>> to input an enumerator
Similarly, we can overload >> to accept an enumerator.
If the user types, large we want CoffeeSize::Large.
#include <iostream>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
enum class CoffeeSize {
Small,
Medium,
Large
};
constexpr std::string_view toString(CoffeeSize size){
switch (size) {
case CoffeeSize::Small: return "small";
case CoffeeSize::Medium: return "medium";
case CoffeeSize::Large: return "large";
}
return "unknown";
}
std::ostream& operator<<(std::ostream& out, CoffeeSize size)
{
return out << toString(size);
}
std::optional<CoffeeSize> parseCoffeeSize(std::string_view text){
if (text == "small") {
return CoffeeSize::Small;
}
if (text == "medium") {
return CoffeeSize::Medium;
}
if (text == "large") {
return CoffeeSize::Large;
}
return std::nullopt;
}
std::istream& operator>>(std::istream& in, CoffeeSize& size)
{
std::string text {};
in >> text;
if (std::optional<CoffeeSize> parsed { parseCoffeeSize(text) }) {
size = *parsed;
return in;
}
in.setstate(std::ios_base::failbit);
return in;
}
int main(){
std::istringstream input { "large" };
CoffeeSize size {};
input >> size;
if (input) {
std::cout << "Order size: " << size << '\n';
} else {
std::cout << "Invalid size\n";
}
}
Terminal output:
Order size: large
The input operator receives the enum by non-const reference:
CoffeeSize& size
That is necessary because the function needs to write the parsed value back into the caller’s object.
The function reads a word from the stream:
in >> text;
Then it attempts to parse that word:
parseCoffeeSize(text)
If parsing succeeds, the enum is assigned:
size = *parsed;
If parsing fails, the stream is placed into the fail state:
in.setstate(std::ios_base::failbit);
This matches normal stream behaviour. Invalid extraction should make the stream fail so the caller can test it with: if (input)or: if (std::cin)
However, there is an easier way to detect invalid input. If the user enters an enumeration that isn’t listed in parseCoffeSize(), std::optional is used
If we order a Mega Large coffee, std::istringstream input { "Mega large" };, the program handles the invalid input gracefully:
Invalid size