bool - the simplest type?
published at 25.08.2022 21:34 by Jens Weller
Save to Instapaper Pocket
A few weeks ago a blog post got me thinking about bool. How simple it looks at the first glance, and how much one can actually say about bool and the way it affects code in C++.
At a first glance, you see that bool is the simplest type in C++. It has only two values: true and false. And thats it one could assume, which some folks will find to be true for *some* of the topics in this and other posts. Its not always the fault of bool, often its also more of a problem shared with other small types and/or values expressed directly in the code like 0, 0.0, 1, true or false. Some of this isn't even restricted to C++, some of this is true in other programming languages too.
The post that got me thinking was about (not) using bool in function parameters. Which I'll get to later. But first lets have a look at bool it self.
sizeof(bool) == sizeof(char)
When we look at the fundamental level of existence of our variables, we find out that they live somewhere in memory. And sizeof can tell us the number of bytes a variable is in size. A bool is as large as a char, yet it only can hold the information of a single bit. A variable in memory has to have an address, so that a pointer or reference can point to it. And the smallest available addressable unit in our systems is a byte with usually 8 bits in it. And this waste of 7 bits is where some of the issues with bool come from or to be honest, were created.
Which brings me to vector< bool >. Unlike what you may think, it might not be a dynamic arrays of bools, as that would not very efficient in memory space. Unfortunately vector< bool > can be a specialization of the vector class. It then behaves internally different then lets say a vector< char > or vector< int >. It may store each of its bools in a single bit, by using a bitfield internally. There is a dedicated page about vector< bool > in cppreference. There is much to say about vector and bool, too much for this post. One of its internals is that it needs a proxy iterator, as each of its bool elements is a bit, you'll need something to iterate over this collection. But you can't take an address of a value inside this vector, as this bool does not exist as an addressable variable in memory. After all its just a bit. Which becomes a problem in generic code, when you handle a container and your code behaves differently with vector< bool >. You might need to guard your code with a constexpr if and so on.
C++ has also a bitset class, which offers a compile time defined size of bits to store your boolean values into. It does not have a dynamic bitset, as std::vector< bool > basically acts as this in many implementations. And this internal difference in not being what vector< bool > *should* be, may come at a cost: sizeof(vector< bool >) is 40 bytes, while its 24 for a vector of char on gcc or clang with GCCs standard library, with libc++ its with 24 equal to a normal vector, while its 32 bytes on MSVC.
A small type on the big bus
As bool is a small type, it can make trouble with alignment. No different to char, short or other small types. In a simple example a struct with bool, int and again a bool will be 12 bytes in memory. If you swap the int with the last bool, its 8 bytes. I don't want to go into the full details why this is. But the compiler likes to have its memory layout in neat packages in a grid of bytes, in this example the grid size is 4 bytes. This may also depend on your implementation and platform. In this case it can pack two bools into a 4 byte block. But the int with its 4 bytes has to go in its own 4 byte block. This is achieved by adding padding bytes into your struct by the compiler. So the true cost of adding a single bool to your struct can be in this case 4 bytes instead of one. One of course can tell the compiler to ignore this, and pack your struct tight to what it could be. But there are good reasons that it is not the default behavior.
Then, one should know in this context about the importance of cache lines. Thats the size of the bus driving your variables memory from the memory to the processor caches. Kind of. This is often a size 64 bytes, but can be 32, 128 or other values depending on the hardware. So when your type fits neatly into such a size, it will have a better time when it comes to running your code.
So when using bool in a struct or class, one should be aware of these effects. Adding the bool and other smaller variables at the end can be a good way to avoid or improve this issue.
paragraph(true,true,false)
Lets quickly talk about bool as a function parameter. This is what the blog post was about which got me thinking in the first place. I think it did a fairly good job in quickly only tackling the issue with folks simply not knowing what values the call is setting. What does the true in a function call represent, and if you have multilpe bools it gets confusing.
One may counter this with having dedicated enums or types, which then also can have the benefit of failing to compile if you set a value as a wrong representation. Again, this issue is not unique to bool, it also occurs with lots of other types. So an enum event_mode {online,onsite} might be better for your code then bool isonsite as an argument. With C++20 designated initializers there is an alternative route. But then you rely on the users of your code writing good code.
bool as an error code
Qt has static member function which you can give a pointer to bool to, to know if this function was canceled. Its an input dialog, and in this way you can know if the user entered an empty string or clicked on cancel for example. Also using the return value of a function for letting the user know if its succeeded is popular. This then can be bool, an error code class like HRESULT. Other code bases use exceptions for error reporting.
Qt chose to not return bool, but rather give the user the option of knowing the difference between an empty string returned and the user clicking on cancel. As in many cases an empty string is not a valid input, you could in this case ignore the bool option. An optional is a valid alternative in this use case.
Conversion to bool
I should quickly mention that bool is one of the types that has a conversion operator and also implicit conversions from other types in general. Which is mostly good, except for the point when you evaluate an expression and it does not do what you expect it to do, which then isn't bools fault. For other types its much more relevant, as a conversion isn't always visible. Think about auto in this context. The implicit conversion is happening in many places, like when you have an if(ptr) or while(file>> str).
One thing you should be aware of is that comparing floating point numbers has many pitfalls. These can lead to "wrong" conversions to bool in various places like if, while, ...
using tribool = std::optional< bool >
In some cases, you'd like to have a type with three states. Boost offers a tribool library, the standard today has std::optional, which may also fit your needs. If you have a case where the third state of tribool is unknown/unitialized/unused, optional will have you covered.
If the 3rd state or value is something else, a variant or union with bool might do it for you.
Friends of bool
There is more then just bool, true or false. Through out history code bases have had their own defines, you might get to meet BOOL,FALSE and TRUE on your journey. On Windows you have HRESULT with macros FAILED and SUCCEDED. Also true and false are not always the best representation for something that is true or false. Enums might choose to name their true or false values YES/NO,ON/OFF,OK/CANCEL.
QBool in Qt was deprecated with Qt5.
Should I mention std::true_type & std::false_type?
These are two types which are representations of integral_constant for bool. They are part of the header , and allow for representing the values of true and false as a type during compile time.
More about bool
There is atomic< bool >, concepts and more...
I'm sure there is more to bool then the above, and indeed lots of folks have written about this. And I just finished a bit of code to find some of them, here is a collection of C++ blog posts having bool in their title, ordered by time:
- CPP Rendering
- Sandor Dargo's Blog
- Jason Turner YT
- Sandor Dargo's Blog
- C++ Stories
- Arthur O'Dwyer
- `view_interface` types are boolean-testable
- Implementation divergence on swapping bools within vectors
- vector of bool
- Fluent C++
- Algorithms on Sets That Return a Boolean: Strong Templates Interface
- Algorithms on Sets That Return a Boolean: Implementing the Generic Algorithm
- Algorithms on Sets That Return a Boolean: Exploring the algorithms
- Hatcat
- The Old New Thing
- Fluent C++
- The Old New Thing
- In C++/WinRT, what happens when I treat an IInspectable as or convert one to a bool
- In C++/CX, hat pointers are contextually convertible to bool, but you can’t always static_cast them to bool
- Bartek's Coding Blog
- Sy Brand
- Fluent C++
- Barry Revzin
- Reading from the inactive member of a union (as you do in operator bool() in the case where we have…
- optional: reference or constexpr?
- Abseil
- Bartek's Coding Blog
- Meeting C++ YT
- Faith and Brave
- Modernes C++
- KDAB
- Faith and Brave
- demofox
- C++ Hints
- isocpp
- Mortoray
- The Deep Blue C++
- Byte Kitchen
Join the Meeting C++ patreon community!
This and other posts on Meeting C++ are enabled by my supporters on patreon!