Understanding your bool type

In this post I’m going to talk about something simple: the bool primitive type in C++. Although it’s simple, it can become quite interesting when you understand how the language/compiler handles this type and what you can achieve by defining your own boolean type.

Let’s start with the basics. A boolean type is used to represent two values: true or false. However, in many languages a boolean type is internally represented (or stored) as an 8 bit integer, which means that it can represents up to 256 different values (not really just true or false).

What I’m saying might not make sense, until you type some crazy code like this:

struct SomeType {
 
      ...
 
      bool isEnabled;
 
  };
 
   
 
  SomeType type;
 
  memset(&type, 0xFF, sizeof(type));
 
   
 
  if (type.isEnabled == true) printf("is enabled");
 
  if (type.isEnabled == false) printf("is NOT enabled");
 
  if (type.isEnabled != true && type.isEnabled != false) printf("OMG");

The code above illustrates how a bool type can actually hold more than just two different values. It also opposes the idea that the value 0×00 represents false, while all other values represent true. In reality, true and false are defined in C++ as the specific values 0×01 and 0×00. In that case, all other values would represent an undefined state.

You might think of some examples that would contradict what I just said, something like:

bool getResult1() {
 
      return 7;    // Lucky number.
 
  }
 
   
 
  bool getResult2(int number1, int number2) {
 
      return number1 & number2;
 
  }
 
   
 
  if (getResult1() == true) printf("You won 1 million dollars.");
 
  if (getResult2(32, 48) == true) printf("You just lost your 1 million dollars.");

The code above will work as you expect, and this is because your compiler normalizes the return value for functions getResult1 and getResult2, transforming/casting the result to a valid bool type. In function getResult2, that would be done by comparing/testing the resulting value against itself, and returning false when there’s no bit set. That’s equivalent to returning false when the value is 0×00 and true in all other cases, what might give you the impression that true can be represented by any value different from 0×00. In function getResult1, the compiler can fix the code by just changing the return value to 0×01.

Why I’m telling you all this? Why would you care about how booleans are stored when the compiler does all the work for you? Well, if you understand what is happening behind the scenes you would able to do interesting things. For example, you can change the way you decide how to use comparisons and logical operations in your code, or even define your own boolean type and try to avoid the normalizations/castings (although that would make you responsible for keeping your booleans in a valid state).

To finish this post, I’ll show one last example of what you can do when you understand how booleans are represented/stored internally. Imagine that you have a Bool type (defined as an uint8_t), and the built-in c++ bool type:

// Approach 1 - All methods return a built-in bool type, operator && also returns a built-in bool.
 
  if (player.isAlive() && player.hasAmmunition() && enemy.isAlive() && player.canSee(enemy)) player.fire();
 
   
 
  // Approach 2 - All methods return a Bool/uint8_t type.
 
  if (player.isAlive() & player.hasAmmunition() & enemy.isAlive() & player.canSee(enemy) == kTrue) player.fire();

If you opt for doing things using the first approach, you will end up having lots of branches in your code. That’s because the first approach might branch after each test, in other words, if player.isAlive() fails you will skip all other tests.

If you opt for doing things using the second approach, you will have to call all the functions listed in the if clause (what might be a lot of work) but you’ll only branch once. In cases where you decide to go with this approach, you’ll need to be careful because things that used to work might end up crashing your application:

// Yes, it works.
 
  if (player != NULL && player->isJumping()) player->applyUltraGravityForce();
 
   
 
  // Undefined behavior. Might call isJumping from a NULL pointer.
 
  if (player != NULL & player->isJumping()) player->applyUltraGravityForce();