A Typesafe Boolean Class for C++

We try to implement a boolean class for C++ that has the same sensible semantics as the Java boolean type.

Handling of boolean values is one of the weaker sides of C++. The C++ bool type was not designed to be typesafe. It is treated as just another integral type, with builtin implicit conversions to numeric types. We adopt the conservative view that implicit conversions should never discard data, as the standard conversion int to bool does.

C++ allows monstrous code like this:

    vector<int> v1 (5);         // OK, vector with 5 elements
    SmartPtr<Foo> p = new Foo;  // implicitly convertible to bool
    vector<int> v2 (p);         // OK, vector with 1 element!

It's so bad that the C++ standard itself avoids providing an  operator bool  for classes that should be usable in a boolean context.

Instead, it uses conversion to  operator void*. The idea is that the type void* is implicitly convertible to bool, but not to int. This fixes some type safety problems, but introduces new ones:

    class X { ... operator void* () { ... } };
    X x;
    delete x;  // Yikes!

A clever safe implicit conversion to bool

Vandevoorde and Josuttis point out a better technique (apparently overlooked by the C++ committee?) in their excellent book C++ Templates.

    class X
      struct BoolConversion { int dummy; };
      inline operator int BoolConversion::* ()
        return ... ? & BoolConversion::dummy : 0;
    X x;
    if (x) { ... } // OK
    delete x;      // COMPILE ERROR

The idea is that there are very few things you can do with a pointer to member of a private class other than implicitly convert it to bool.

Vandevoorde and Josuttis use this technique for smart pointers, but we want to use it to create a class Bool, a typesafe replacement for bool. In order for our class to be used in boolean contexts, it must be implicitly convertible to bool. We will use a variant of the BoolConversion trick:

    class Bool
      const bool val_;
      struct S_ { int M_; };
      typedef int S_::* bool_;
      inline bool_ true_  () const { return & S_::M_; }
      inline bool_ false_ () const { return 0; }
      operator bool_ () const { return val_ ? true_ () : false_ (); }

This prevents dangerous implicit outward conversions. Of course, users that want to use a Bool in a numeric context can do

    Bool b;
    int x = (bool) b;
although we think this is poor style and prefer
    Bool b;
    int x = b ? 1 : 0;

What about Inward Conversions?

Let's start with the obvious. Our Bool class should be implicitly convertible from bool.

    class Bool
      Bool (bool val) : val_ (val)   {}

If we leave it at that, we have:

Unfortunately, this is the inverse of the desired conversions. We are really fighting the C++ type system here. C++ favors standard conversions between builtin types, relegating user-defined conversions to second-class citizen status. We want our class to wrest control of the conversion process.

Casting off the Tyranny of Implicit Standard Conversions

As usual, our best weapon in this battle with the C++ type system is template wizardry. The key observation is this — a normal function

    void foo (bool) { ... }
can actually be called with an argument that is subjected to a sequence of three conversions: a standard conversion followed by a user-defined conversion followed by another standard conversion, but function template arguments have to match exactly. If we want our function foo to accept arguments of type bool and only type bool, with no conversions allowed, we can use the advanced techniques developed in Constraints for Function Template Parameters in C++:
    template <bool condition, typename type = int> struct Constraint;
    template <typename type> struct Constraint<true, type>
      typedef type Type;

    template <class T>
    void foo (T x,
              typename Constraint<boost::is_same<T, bool>::value>::Type = 0)
    { ... }

We apply the same idea to the converting constructors for Bool.

First, we discard the "obviously correct"  Bool::Bool (bool)  constructor — it was just an implementation trap.

We choose to allow implicit conversions to Bool only for types bool and (of course) Bool, but allow explicit conversions for types that are, like Bool itself, implicitly convertible to bool, but not convertible to int or void*.

    class Bool
      // ----------------------------------------------------------------
      // Allow implicit conversions from bool, and _only_ from bool.
      // Template constructors are not chained with standard conversions,
      // so no implicit conversions from int, for example, are allowed.
      template <typename T>
      struct ImplicitlyConvertible
	enum { value = boost::is_same<T,bool>::value };

      // ----------------------------------------------------------------
      // Allow explicit conversions from any type that, like Bool itself,
      // is convertible to bool, but not to int or void*.
      template <typename T>
      struct ExplicitlyConvertible
	enum { value = (! ImplicitlyConvertible<T>::value &&
			(boost::is_convertible<T,bool>::value &&
			 ! boost::is_convertible<T,int>::value &&
			 ! boost::is_convertible<T,void*>::value)) };

      template <typename T>
      Bool (T x, typename Constraint<ImplicitlyConvertible<T>::value>::Type = 0)
	: val_ (x)

      template <typename T>
      Bool (T x, typename Constraint<ExplicitlyConvertible<T>::value>::Type = 0)
	: val_ (x)

It's not clear whether this relatively arbitrary set of conversion rules is the best in practice. But at least we're in control of the types we accept.

True and False

What about class literals? The literal true is convertible to int. We want a Bool literal TRUE convertible to bool, but not int.

We use the Class Literal idiom:

    class Bool
      // Dummy constructor used by True and False
      Bool () : val_ (/* value unused */ false) {}
      class Literal;

      class True;  Bool (const True &) : val_ (true ) {}
      class False; Bool (const False&) : val_ (false) {}

    class Bool::Literal : protected Bool
      Literal () {}
      // Prohibit nonsensical operations
      void* operator new (size_t);
      void  operator delete (void*);
      void* operator& ();
      Literal& operator= (const Literal&);

    class Bool::True : private Literal
      operator bool_  () const { return true_(); }
      inline False operator! () const { return FALSE; }

    class Bool::False : private Literal
      operator Bool::bool_  () const { return false_(); }
      inline True operator! () const { return TRUE;  }

    // Bool literals
    // The objects themselves are unused, and can be deleted by a good linker.
    extern Bool::True  TRUE;
    extern Bool::False FALSE;

How close are we to Java's boolean?

We have achieved the following tightening up of the C++ type system:

    Bool t = TRUE;
    void f (Bool);
    void g (int);
    f (true);      // OK
    f (t);         // OK
    f (TRUE);      // OK
    f (42);        // COMPILE ERROR
    g (1);         // OK
    g (t);         // COMPILE ERROR
    & TRUE;        // COMPILE ERROR
    t == true;     // OK
    t == 1;        // COMPILE ERROR
    new Bool::True;// COMPILE ERROR

The Real World

The Bool class presented here is definitely experimental. It remains to be seen whether it can become a practical tool.

We need practical experience to find the best tradeoff between type safety and programming convenience. Future plans for the MObS object system involve incorporating a type such as Bool.

Unfortunately, we cannot rely on current C++ compilers (e.g. g++ fails) to compile code using Bool as efficiently as code using bool. In the real world we might adopt a solution such as this:

#include "Bool.hpp"
typedef bool Bool;
#define TRUE true
#define FALSE false

Since all error checking is done at compile time, there is no reason to use an error-checking version in production mode.

Some of the techniques presented here are too obscure for ordinary users to have to deal with. We are drifting towards a world where we don't write any ordinary functions any more, only template functions. This works better if C++ is a target language, rather than a "real" programming language.

We have not eliminated all the pitfalls involved in type conversion. If the user defines a function overloaded on types int and Bool, we get

    void f (Bool);
    void f (int);
    f (5);           // OK -- f (int)
    f (5L);          // OK -- f (int)
    f (TRUE);        // OK -- f (Bool)
    f (Bool (true)); // OK -- f (Bool)
    f (true);        // BAD -- f (int)

The author of f can prevent this problem by templatizing f as above, but this is too much to expect. Instead we (the authors of the infrastructure) can be consistent and replace int with a typesafe Int just as we have done with Bool.

    void f (Bool);
    void f (Int);
    f (5);        // OK -- f (Int)
    f ((short)5); // OK -- f (Int)
    f (5L);       // COMPILE ERROR -- Sorry, no implicit narrowing conversions
    f (TRUE);     // OK -- f (Bool)
    f (true);     // OK -- f (Bool)

If we follow through on this idea, we will eventually be writing C++ programs that don't use any of the fundamental types directly.

It shouldn't be this difficult to create reasonable abstractions. This Bool class combines three different highly non-trivial techniques. How are we supposed to implement something that is actually intrinsically difficult?

An actual working program

A sample program using the Bool class (with test suite included) is available here.

Back to Martin's home page
Last modified: Mon Jan 27 01:56:23 PST 2003
Copyright © 2003 Martin Buchholz