Last Update:
Ways to Refactor Toggle/Boolean Parameters in C++
Table of Contents
Boolean parameters in a function might be misleading and decrease its readability. If you have a poorly named function like:
DoImportantStuff(true, false, true, false);
As you can imagine, it’s not clear what all those parameters mean? What’s the first true
? What does the last false
mean? Can we improve code in such cases?
Let’s have a look at possible improvements.
Intro
This article was motivated by a similar text that appeared on Andrzej Krzemienski’s Blog: Toggles in functions.
As Andrzej wrote, the whole point is to improve the code around functions like:
RenderGlyphs(glyphs, true, false, true, false);
What if you mix two parameters and change their order? The compiler won’t help you much!
Let’s think about improving the code: make it safer and more readable.
We could add comments:
RenderGlyphs(glyphs,
/*useChache*/true,
/*deferred*/false,
/*optimize*/true,
/*finalRender*/false);
And although the above code is a bit more readable, we still don’t get any more safety.
Can we do more?
Ideas
Here are some ideas that you can use to make such code better.
Small enums
We could write the following declarations:
enum class UseCacheFlag { False, True };
enum class DeferredFlag { False, True };
enum class OptimizeFlag { False, True };
enum class FinalRenderFlag { False, True };
// and call like:
RenderGlyphs(glyphs,
UseCacheFlag::True,
DeferredFlag::False,
OptimizeFlag::True,
FinalRenderFlag::False);
And in the implementation you need to change:
if (useCache) { }
else { }
if (deferred) { }
else {}
To proper comparison:
if (useCache == UseCacheFlag::True) { }
else { }
if (deferred == DeferredFlag::True) { }
else {}
As you can see, you need to check against enum values rather than just check the bool value.
Using enums is a good approach, but it has some disadvantages:
- A lot of additional names are required!
- Maybe we could reuse some types. Should we have some common flags defined in the project? How to organize those types?
- Values are not directly convertible to bool, so you have to compare against
Flag::True
explicitly inside the function body.
The required explicit comparison was the reason Andrzej wrote his own little library that creates toggles with conversion to bool
.
I was disappointed that we don’t have direct support from the language for strong types for enums. But after a while, I changed my mind. The explicit comparison is not that hard to write, so maybe it would be overkill to include it in the language spec? Introducing explicit casts might even cause some problems.
Still, I am not entirely happy with the need to write so many tiny enums…
Bit flags
As a potential evolution for enums, you can also use bit flags.
Unfortunately, we don’t have friendly and type-safe support from the language, so you need to add some boilerplate code to support all operations.
Here’s my simplified approach:
#include <type_traits>
struct Glyphs { };
enum class RenderGlyphsFlags
{
useCache = 1,
deferred = 2,
optimize = 4,
finalRender = 8,
};
// simplification...
RenderGlyphsFlags operator | (RenderGlyphsFlags a, RenderGlyphsFlags b) {
using T = std::underlying_type_t <RenderGlyphsFlags>;
return static_cast<RenderGlyphsFlags>(static_cast<T>(a) | static_cast<T>(b));
// todo: missing check if the new value is in range...
}
constexpr bool IsSet(RenderGlyphsFlags val, RenderGlyphsFlags check) {
using T = std::underlying_type_t <RenderGlyphsFlags>;
return static_cast<T>(val) & static_cast<T>(check);
// todo: missing additional checks...
}
void RenderGlyphs(Glyphs &glyphs, RenderGlyphsFlags flags)
{
if (IsSet(flags, RenderGlyphsFlags::useCache)) { }
else { }
if (IsSet(flags, RenderGlyphsFlags::deferred)) { }
else { }
// ...
}
int main() {
Glyphs glyphs;
RenderGlyphs(glyphs, RenderGlyphsFlags::useCache | RenderGlyphsFlags::optimize);
}
Play @Compiler Explorer.
What do you think about this approach? With some additional code and operator overloading, we can end up with a nice function that is readable and typesafe. If you add more checks into my example code, you can enforce that the values you pass have the right bit set.
Since C++23 you can also try usingstd::to_underlying()
, from the<utility>
header. See my example at @Compiler Explorer as this function is already implemented in GCC, Clang and MSVC.
Param structure
If you have several parameters (like 4 or 5, depending on the context), why don’t we wrap them into a separate structure?
struct RenderGlyphsParam
{
bool useCache;
bool deferred;
bool optimize;
bool finalRender;
};
void RenderGlyphs(Glyphs &glyphs, const RenderGlyphsParam &renderParam);
// the call:
RenderGlyphs(glyphs,
{/*useCache*/true,
/*deferred*/false,
/*optimize*/true,
/*finalRender*/false});
OK… this didn’t help much! You get additional code to manage, and the caller uses almost the same code.
Yet, this approach has the following advantages:
- It moves the problem to the other place. You could apply strong types to individual members of the structure.
- If you need to add more parameters, you can just extend the structure.
- Especially useful if more functions can share such param structure.
Side note: you could put the glyphs
variable also in the RenderGlyphsParam
, this is only for example.
How about C++20?
Thanks to Designated Initializers that landed in C++20, we can use “named” parameters when constructing our small structure.
Basically, you could use a similar approach as in C99 and name arguments that you pass to a function:
struct RenderGlyphsParam
{
bool useCache;
bool deferred;
bool optimize;
bool finalRender;
};
void RenderGlyphs(Glyphs &glyphs, const RenderGlyphsParam &renderParam);
// the call:
RenderGlyphs(glyphs,
{.useCache = true,
.deferred = false,
.optimize = true,
.finalRender = false});
Play @Compiler Explorer.
You can read my blog post on this new feature here: Designated Initializers in C++20 - C++ Stories.
Elimination
We could try to fix the syntax and use clever techniques. But what about using a simpler method? What if we provide more functions and just eliminate the parameter?
It’s OK to have one or two toggle parameters, but if you have more, maybe it means a function tries to do too much?
In our simple example, we could try the split in the following way:
RenderGlyphsDeferred(glyphs,
/*useCache*/true,
/*optimize*/true);
RenderGlyphsForFinalRender(glyphs,
/*useCache*/true,
/*optimize*/true;
We can make the change for parameters that are mutually exclusive. In our example, deferred cannot happen together with the final run.
You might have some internal function RenderGlyphsInternal
that would still take those toggle parameters (if you really cannot separate the code). But at least such internal code will be hidden from the public API. You can refactor that internal function later if possible.
I think it’s good to look at the function declaration and review if there are mutually exclusive parameters. Maybe the function is doing too much? If yes, then cut it into several smaller functions.
After writing this section, I’ve noticed a tip from Martin Fowler on Flag Arguments. In the text, he also tries to avoid toggles.
You can also read this article from Robert C. Martin’s Clean Code Tip #12: Eliminate Boolean Arguments. And more in his book Clean Code: A Handbook of Agile Software Craftsmanship
Stronger types
Using small enums or structures is a part of a more general topic of using Stronger Types. Similar problems might appear when you have several ints as parameters or strings…
You can read more about:
- Strong Types in C++: A Concrete Example - C++ Stories
- Simplify C++: Use Stronger Types! -
- Type safe handles in C++ — I Like Big Bits
- Strong types for strong interfaces - Fluent C++
- foonathan::blog() - Type safe - Zero overhead utilities for more type safety
- Serialization - BOOST_STATIC_WARNING
C++ Guidelines
Thankfully we also have C++ Guidelines, and we can reach for help here.
There’s an item: I.4: Make interfaces precisely and strongly typed which not only talks about boolean parameters but all sorts of potentially misleading names.
For instance, the guidelines mention the following cases:
draw_rect(100, 200, 100, 500); // what do the numbers specify?
draw_rect(p.x, p.y, 10, 20); // what units are 10 and 20 in?
As an improvement, we can use the following approaches:
- Pass a separate structure so that the arguments converts into data members
- Consider using a flags enum
- Consider using strong types, for example passing
std::chrono::milliseconds
rather thanint num_msec
to a function.
What’s more, as potential enforcement from the code analysis tools they suggest:
Look for functions that use too many primitive type arguments
Tools
Speaking of tooling, one reader suggested that in Clang-Tidy there’s a check that enforces “named parameters comments” near the arguments.
This feature is called: clang-tidy - bugprone-argument-comment — Extra Clang Tools 15.0.0git documentation.
For example:
void RenderGlyphs(Glyphs &glyphs,
bool useCache, bool deferred, bool optimize, bool finalRender, int bpp)
{
}
int main() {
Glyphs glyphs;
RenderGlyphs(glyphs,
/*useCha=*/true,
/*deferred=*/false,
/*optimize=*/true,
/*finalRender=*/false,
/*bpppp=*/8);
}
You’ll get the following message:
<source>:13:14: warning: argument name 'useCha' in comment does not
match parameter name 'useCache' [bugprone-argument-comment]
/*useCha=*/true,
^
<source>:5:8: note: 'useCache' declared here
bool useCache, bool deferred, bool optimize, bool finalRender, int bpp)
^
The comment has to be in the form of /*arg=*/
.
See the example @Compiler Explorer.
A concrete example
Recently, I had a chance to apply some ideas of enum/stronger types to my code. Here’s a rough outline:
// functions:
bool CreateContainer(Container *pOutContainer, bool *pOutWasReused);
void Process(Container *pContainer, bool bWasReused);
// usage
bool bWasReused = false;
if (!CreateContainer(&myContainer, &bWasReused))
return false;
Process(&myContainer, bWasReused);
Briefly: we create a container, and we process it. The container might be reused (through a pool, reusing existing objects, etc., some internal logic).
I thought that it didn’t look nice. We use one output flag, and then it’s passed as input to some other function.
What’s more, we pass pointers, and some additional validation should happen. Also, the output parameters are discouraged in Modern C++, so it’s not good to have them anyway.
How can we do better?
Let’s use enums!
enum class ContainerCreateInfo { Err, Created, Reused };
ContainerCreateInfo CreateContainer(Container *pOutContainer);
void Process(Container *pContainer, ContainerCreateInfo createInfo);
// usage
auto createInfo = CreateContainer(&myContainer)
if (createInfo == ContainerCreateInfo::Err);
return false;
Process(&myContainer, createInfo);
Isn’t it better?
There are no outputs via pointer stuff here; we have a strong type for the ‘toggle’ parameter.
Also, if you need to pass some more information in that CreateInfo
enum, you can just add one more enum value and process it in proper places; the function prototypes don’t have to change.
Of course, in the implementation, you have to compare against enum values (not just cast to bool
), but it is not difficult and even more verbose.
Is that all?
The code is still not perfect as I have pOutContainer
, which is not ideal.
In my real project, that was a complex thing to change, and I wanted to reuse existing containers… But if your container support move semantics and you can rely on Return Value Optimization, then it’s possible to return it:
enum class ContainerCreateInfo { Err, Created, Reused };
std::pair<Container, ContainerCreateInfo> CreateContainer();
Our function becomes a factory function, but it has to return some additional information about the creation process.
We can use it as follows:
// usage
auto [myContainer, createInfo] = CreateContainer()
if (createInfo == ContainerCreateInfo::Err);
return false;
Process(&myContainer, createInfo);
Summary
By reading the original article from Andrzej and these additional few words from me, I hope you get the idea about toggle type parameters. They are not totally wrong, and it’s probably impossible to avoid them entirely. Still, it’s better to review your design when you want to add third or fourth parameter in a row :) Maybe you can reduce the number of toggles/flags and have more expressive code?
More to read:
- Toggles in functions | Andrzej’s C++ blog
- What is wrong with boolean parameters? @Understand Legacy Code
- c++11 - Using scoped enums for bit flags in C++ - Software Engineering Stack Exchange
Back to you
- Do you try to refactor toggle parameters?
- Do you use strong types in your code?
Share your feedback in the comments below the article.
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here: