When faced with passing multiple options as arguments to functions, C++ programmers will use enum class
without much hesitation. Since C+11, it feels like we have moved away from magic numbers and magic string values, and in most cases, it’s for the best. There seems to be one exception: when we only have two options to choose from.
Here, I will try to convince you to stop overusing bools that don’t convey true/false or yes/no.
Rule of thumb
For making interfaces safer, more expressive, and nicer to use, I will advocate for the following rule of thumb:
If you read out the function call with its arguments, bool arguments and return values must be able to be substituted with true/false or yes/no.
When bools are appropriate
For example: the C++ function void MyClass::set_done(bool done);
fits this perfectly. If we call foo.set_done(true);
, we can read this as “Set done to true”. In the same way, calling bool MyClass::is_done() const;
with foo.is_done()
can be read out as “Is it done? Yes/no.”.
When bools are not appropriate
We often use bools in cases where the argument or answer cannot be yes or no. Using enums instead will provide clarity at the call site, without us having to look up definitions of the arguments.
struct BadWindow {
void show_window(bool showInForeground);
};enum class WindowOpenMode {
Foreground,
Background,
};struct GoodWindow {
void show_window(WindowOpenMode mode);
};int main() {
BadWindow badWindow;
GoodWindow goodWindow; // show window yes?
badWindow.show_window(true);
// show window in the foreground!
goodWindow.show_window(WindowOpenMode::Foreground);
}
Even though we only have one argument for this function, using enums has already made the call sites much clearer. Imagine how much of a difference this approach will make for functions with multiple binary choices!
// this
do_stuff(true, false, false);
// can turn into this
do_stuff(SuccessAction::Report,
FailureAction::None,
Pants::None);
Preventing bugs by dropping bools
I think I have demonstrated how using enums can make our interfaces nicer to use and more expressive, but I have yet to show how they are safer.
In C++, argument names in declarations don’t have to match the definition. When using bools, it’s possible to make a mistake (or forget to flip the declaration when refactoring). Then, if your caller only looks at your header file, he may use the other bool value than they may intend.
// show_window.h
void show_window(bool showBackground);// show_window.cpp
// which of these two should it be, anyways?
void show_window(bool showForeground) {
// ...
}
This mistake may not be particurarly common, but it’s possible to get this wrong by accident. By switching to an enum class
argument here, this mistake becomes impossible to make:
// show_window.h
enum class WindowOpenMode {
Foreground,
Background
};
void show_window(WindowOpenMode mode);// show_window.cpp
// the argument now always means the same thing
void show_window(WindowOpenMode mode) {
// ...
}
Final word
We programmers are often lazy, so booleans come as an easy substitute for defining what we actually want to convey in arguments and return values. But we usually spend most of our time reading and using code, not writing it. With just a little more effort, we can make reading our code a better experience for everyone.