More C++ Idioms/Safe bool

From Wikibooks, the open-content textbooks collection

< More C++ Idioms
Jump to: navigation, search

Contents

[edit]

Safe Bool

[edit] Intent

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

[edit] Also Known As

[edit] Motivation

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;
}

[edit] Solution and Sample Code

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_==true ? 
        &Testable::this_type_does_not_support_comparisons : 0;
    }
  };
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;
 
      safe_bool_base::this_type_does_not_support_comparisons();
    }
  protected:
    virtual bool boolean_test() const=0;
    virtual ~safe_bool() {}
  };
 
  template <typename T> 
    bool operator==(const safe_bool<T>& lhs, bool b) {
      if (b)
      {
          if (lhs) return true;
          else return false;
      }
      else
      {
          if (lhs) return false;
          else return true;
      }
  }
 
  template <typename T> 
    bool operator==(bool b, const safe_bool<T>& rhs) {
      if (b)
      {
          if (rhs) return true;
          else return false;
      }
      else
      {
          if (rhs) return false;
          else return true;
      }
  }
 
 
  template <typename T, typename U> 
    void operator==(const safe_bool<T>& lhs,const safe_bool<U>& rhs) {
      lhs.this_type_does_not_support_comparisons();  
  }
 
  template <typename T,typename U> 
  void operator!=(const safe_bool<T>& lhs,const safe_bool<U>& rhs) {
    lhs.this_type_does_not_support_comparisons();
  }

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. Unfortunately, there is a access control bug in g++ compiler that allows taking address of safe_bool_base::this_type_does_not_support_comparisons in a class template even when it is declared protected.

[edit] Known Uses

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

[edit] Related Idioms

[edit] References

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

Personal tools
Create a book
  • Add wiki page
  • Collections help
In other languages