More C++ Idioms/Safe bool

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Safe Bool
[edit | edit source]

Intent[edit | edit source]

To provide boolean tests for a class but restricting it from taking participation in unwanted expressions.

Also Known As[edit | edit source]

Motivation[edit | edit source]

User provided boolean conversion functions can cause more harm than benefit because it allows them to participate in expressions you would not ideally want them to. If a simple conversion operator is defined then two or more objects of unrelated classes can be compared. Type safety is compromised. For example,

struct Testable
{
    operator bool() const {
          return false;
    }
};
struct AnotherTestable
{
    operator bool() const {
          return true;
    }
};
int main (void)
{
  Testable a;
  AnotherTestable b;
  if (a == b) { /* blah blah blah*/ }
  if (a < 0) { /* blah blah blah*/ }
  // The above comparisons are accidental and are not intended but the compiler happily compiles them.
  return 0;
}

Solution and Sample Code[edit | edit source]

Safe bool idiom allows syntactical convenience of testing using an intuitive if statement but at the same time prevents unintended statements unknowingly getting compiled in. Here is the code for the safe bool idiom.

class Testable {
    bool ok_;
    typedef void (Testable::*bool_type)() const;
    void this_type_does_not_support_comparisons() const {}
  public:
    explicit Testable(bool b=true):ok_(b) {}

    operator bool_type() const {
      return ok_ ? 
        &Testable::this_type_does_not_support_comparisons : 0;
    }
};
template <typename T>
bool operator!=(const Testable& lhs, const T&) {
    lhs.this_type_does_not_support_comparisons();	
    return false;
}
template <typename T>
bool operator==(const Testable& lhs, const T&) {
    lhs.this_type_does_not_support_comparisons();
    return false;
}
class AnotherTestable ... // Identical to Testable.
{};
int main (void)
{
  Testable t1;
  AnotherTestable t2;
  if (t1) {} // Works as expected
  if (t2 == t1) {} // Fails to compile
  if (t1 < 0) {} // Fails to compile

  return 0;
}

Reusable Solution

There are two plausible solutions: Using a base class with a virtual function for the actual logic, or a base class that knows which function to call on the derived class. As virtual functions come at a cost (especially if the class you're augmenting with Boolean tests doesn't contain any other virtual functions). See both versions below:

class safe_bool_base {
  public:
    typedef void (safe_bool_base::*bool_type)() const;
    void this_type_does_not_support_comparisons() const {}
  protected:
 
    safe_bool_base() {}
    safe_bool_base(const safe_bool_base&) {}
    safe_bool_base& operator=(const safe_bool_base&) {return *this;}
    ~safe_bool_base() {}
};

// For testability without virtual function.
template <typename T=void> 
class safe_bool : private safe_bool_base {
  // private or protected inheritance is very important here as it triggers the
  // access control violation in main.
  public:
    operator bool_type() const {
      return (static_cast<const T*>(this))->boolean_test()
        ? &safe_bool_base::this_type_does_not_support_comparisons : 0;
    }
  protected:
    ~safe_bool() {}
};

 
// For testability with a virtual function.
template<> 
class safe_bool<void> : private safe_bool_base {
  // private or protected inheritance is very important here as it triggers the
  // access control violation in main.
  public:
    operator bool_type() const {
      return boolean_test() 
        ? &safe_bool_base::this_type_does_not_support_comparisons : 0;
    }
  protected:
    virtual bool boolean_test() const=0;
    virtual ~safe_bool() {}
};
 
template <typename T> 
   bool operator==(const safe_bool<T>& lhs, bool b) {
      return b == static_cast<bool>(lhs);
  }
 
template <typename T> 
   bool operator==(bool b, const safe_bool<T>& rhs) {
      return b == static_cast<bool>(rhs);
  }
 
 
template <typename T, typename U> 
  bool operator==(const safe_bool<T>& lhs,const safe_bool<U>& rhs) {
      lhs.this_type_does_not_support_comparisons();
      return false;
  }
 
template <typename T,typename U> 
  bool operator!=(const safe_bool<T>& lhs,const safe_bool<U>& rhs) {
    lhs.this_type_does_not_support_comparisons();
    return false;
  }

Here's how to use safe_bool:

#include <iostream>

class Testable_with_virtual : public safe_bool<> {
  public:
    virtual ~Testable_with_virtual () {}
  protected:
    virtual bool boolean_test() const {
      // Perform Boolean logic here
      return true;
    }
  };
 
 class Testable_without_virtual : 
    public safe_bool <Testable_without_virtual> // CRTP idiom
 {
  public:
    /* NOT virtual */ bool boolean_test() const {
      // Perform Boolean logic here
      return false;
    }
  };

int main (void)
{
  Testable_with_virtual t1, t2;
  Testable_without_virtual p1, p2;
  if (t1) {}
  if (p1 == false) 
  {
    std::cout << "p1 == false\n";
  }
  if (p1 == p2) {} // Does not compile, as expected
  if (t1 != t2) {} // Does not compile, as expected

  return 0;
}

In C++, address of protected members functions can't be taken in a derived class. Derived class could be a standard class, a class template or a specialization of a class template. Some implementations of safe bool idiom declare safe_bool_base::this_type_does_not_support_comparisons as protected, address of which can't be taken in the derived class - a requirement in reusable safe bool idiom.

An insightful discussion on the boost mailing list initiated by Krzysztof Czainski resulted in an implementation of safe bool idiom using CRTP as well as another using macros.

C++11[edit | edit source]

The C++11 standard provides explicit conversion operators as a parallel to explicit constructors. This new feature solves the problem in a clean way.[1][2][3]

struct Testable
{
    explicit operator bool() const {
          return false;
    }
};

int main()
{
  Testable a, b;
  if (a)      { /*do something*/ }  // this is correct
  if (a == b) { /*do something*/ }  // compiler error
}

Known Uses[edit | edit source]

  • boost::scoped_ptr
  • boost::shared_ptr
  • boost::optional
  • boost::tribool

Related Idioms[edit | edit source]

References[edit | edit source]

http://www.artima.com/cppsource/safebool.html

  1. N2437: Explicit Conversion Operator Draft Working Paper
  2. http://stackoverflow.com/questions/6242768/is-the-safe-bool-idiom-obsolete-in-c11
  3. http://stackoverflow.com/questions/13193766/isset-or-operator-void-or-explicit-opertor-bool-or-something-else