Consider a legacy codebase in C++ that uses int instead of bool. It’s so legacy that it is probably C89, really, before the advent of the bool type in any form. So typical code that has a “boolean” variable as a class member variable looks like this:

class Example {
public:
    Example() : m_toggle(0) { }
    void SetIt(int v) { m_toggle = v; }
    void ToggleIt() { if (m_toggle) { m_toggle = 0; } else { m_toggle = 1; } }
    const char * Status() { if (m_toggle) return "ON"; else return "OFF"; }
private:
    int m_toggle;
};

There is an int in this class, but we use it like a boolean value. Except for SetIt(), there is sort-of-an-invariant that the value is either 0 or 1.

Suppose I wake up and want to replace that int by a bool, but in such a way that every use of that variable with an integer value – and not a boolean constant – will trigger a compile error? In other words, what type T do I need so that the following does-it-compile holds?

T t = 0; // error
T t{17}; // error
T t = false; // ok
T t{true}; // ok

Simple Replacement

Just replacing int with bool does not help. It is simple though: the rest of the code does not need to change. But the code still compiles, and so we are left with all the undesirable int-to-bool conversions.

Less Simple Replacement

We introduce a struct strong_bool that just wraps a single bool. We use this type instead of int or bool in the Example class. Trivially, it looks like this:

struct strong_bool
{
    bool b = false;
};

This flags down a whole bunch of uses already! Unfortunately it also flags the if (m_toggle) use (which ought to be ok: that’s a boolean in a boolean context), and uniform initialization with an int is acceptable.

Advanced Replacement

Let’s give the struct some stronger behavioral guarantees. We can turn it into a class (so there is no access to the inner bool b anymore), and provide it with:

  • a constructor, so that it can be created from a bool,
  • no other constructors, so that it cannot be created from anything but a bool,
  • a boolean-conversion operator, so it can work as a bool in some contexts.
class strong_bool
{
    bool b = false;
public:
    strong_bool() = default;
    template<typename T> strong_bool(T v) = delete;
    strong_bool(bool v) : b(v) {};
    explicit operator bool() const { return b; }
};

Templates to the rescue. It is possible to delete overloads of member functions, including constructors, so here, all of them are deleted (template<typename T>) all of them, and there is one non-template overload taking bool. The trick here is that the template matches first, so passing an int, or a pointer, or whatever, matches that type first – and that overload is deleted.

Edit 2022-11-29 Meanshile, cppcoach (his site) points out that my phrasing is poor here: “matches first” is not standards language. An overload set is formed, and the best match is chosen – which may be the deleted one.

Example, Repaired

Using the new templated version of strong_bool, we can convert the example class to use it. Each use of an int or integer constant where we really meant a bool value is flagged as an error by the compiler, so we can use those as a guide and end up with this:

class Example {
public:
    Example() = default;
    void SetIt(bool v) { m_toggle = v; }
    void ToggleIt() { if (m_toggle) { m_toggle = false; } else { m_toggle = true; } }
    const char * Status() { if (m_toggle) return "ON"; else return "OFF"; }
private:
    strong_bool m_toggle = false;
};

All’s well, right? Well, almost: that bool in the SetIt() method is a not-so-strong bool, so integral values can be passed in. They convert to bool following the standard rules. A call SetIt(3) is just fine. In other words, we have the internal use of the booleans repaired, but not the external API.

Switching the API type so that SetIt() also takes a strong_bool might not be possible, depending on the API stability guarantees given by the codebase. But if it is possible, then replacing that type in the API by a strong_bool would do the trick.

What About Performance?

This struct has the same size and alignment properties as a bool. It has size 1 (at least when compiled with Clang). With just about any optimizations enabled, the compiler can turn all reads and writes to the “inner” bool b into just that: writes to that value. The example program (e.g. Example 6) is optimized to call puts() three times, since the compiler can figure out what value b has anyway, and there is no need to keep the strong_bool or anything else around.

You don’t pay for what you don’t use, but here you get type safety for free!

Takeaway

With a simple type wrapper, you can use the compiler as a tool to hunt down unwanted conversions and dubious use-an-int-as-a-bool use in legacy code. And it’s gratis at runtime, and simple to implement at compile-time.

My codebase only has if-statements and assignements-with-int-constants, so the replacement does not need to be very complicated. Completing the API of strong_bool for a more complicated codebase is left as an exercise.