The Artima Developer Community
The C++ Source | C++ Community News | Discuss | Print | Email | First Page | Previous | Next
Sponsored Link

The C++ Source
The Safe Bool Idiom
by Bjorn Karlsson
July 31, 2004

Page 1 of 3  >>

Advertisement

Summary
Learn how to validate objects in a boolean context without the usual harmful side effects.

In C++, there are a number of ways to provide Boolean tests for classes. Such support is either provided to make usage intuitive, to support generic programming, or both. We shall examine four popular ways of adding support for the popular and idiomatic if (object) {} construct. To conclude, we will discuss a new solution, without the pitfalls and dangers of the other four. Let the games begin.

The Goal

Some types, for example pointers, allow us to test their validity in Boolean contexts. Any rvalue of arithmetic, enumeration, pointer, or pointer to member type, can be implicitly converted to an rvalue of type bool. We frequently use this property to select a branch of code to execute, for example when acquiring a resource:

  if (some_type* p=get_some_type()) {
    // p is valid, use it
  }
  else {
    // p is not valid, take proper action
  }

Of course, such usage is not only useful for built-in types; any type with an unambiguous meaning of validity could greatly benefit from such a Boolean conversion. The alternative is to use a member function for testing. As an example, consider testing a smart pointer (without an implicit conversion to the contained pointer) for validity:

  smart_ptr<some_type> p(get_some_type());
  if (p.is_valid()) {
    // p is valid, use it
  }
  else {
    // p is not valid, take proper action
  }

Besides being more verbose, this version differs from the previous in that the name p needs to be declared outside of the scope in which it is used. This is bad from a maintenance perspective. Also, the name is_valid will probably differ depending on the type of smart pointer at use�it can just as well be is_empty, Empty, Valid, or any other name a creative designer might have thought of when creating it. Finally, even when disregarding the naming issue and the problem with declaration scope, for smart pointers there's the very real requirement to support pointer-like use. It should typically be possible to convert existing code to make use of smart pointers rather than raw pointers, with a minimum of change to the code base, e.g., code like this should work regardless of pointer smartness:

  template <typename T> void some_func(const T& t) {
    if (t)    
      t->print();
  }

Without some conversion to a Boolean testable type, the above if-statement won't compile for smart pointers. The goal that we set out to accomplish in this article is making that conversion safe. As we shall see, that's a bit harder than one would imagine at first glance.

The Obvious Approach Is operator bool

This classical approach has a straightforward implementation. I'll use the same class (Testable) throughout this article, as seen in the following code:

   // operator bool version
  class Testable {
    bool ok_;
  public:
    explicit Testable(bool b=true):ok_(b) {}

    operator bool() const {
      return ok_;
    }
  };

  // operator! version
  class Testable {
    bool not_ok_;
  public:
    explicit Testable(bool b=true):not_ok_(!b) {}

    bool operator!() const {
      return not_ok_;
    }
  };

  // operator void* version
  class Testable {
    bool ok_;
  public:
    explicit Testable(bool b=true):ok_(b) {}

    operator void*() const {
      return ok_==true ? this : 0;
    }
  };

  // nested class version
  class Testable {
    bool ok_;
  public:
    explicit Testable(bool b=true):ok_(b) {}

    class nested_class {};

    operator const nested_class*() const {
      return ok_ ? reinterpret_cast<const nested_class*>(this) : 0;
    }
  };

Note the implementation for the conversion function:

  operator bool() const {
    return ok_;
  }

Now, we can use instances of the class in expressions like this:

  Testable test;
  if (test) 
    std::cout << "Yes, test is working!\n";
  else 
    std::cout << "No, test is not working!\n";

That's fine, but there's a nasty caveat to this as the conversion function has just told the compiler that it's free to do things behind our backs (lesson 0: never trust a compiler to do your job for you; at least not to do it properly);

  test << 1;
  int i=test;

These are both nonsense operations, but yet allowed and legal C++ (we also have the issue of overloading to consider, which makes things even worse). So, operator bool is not a very good approach. We're also able to compare any types that utilize this technique with each other, although that rarely makes sense:

  Testable a;
  AnotherTestable b;

  if (a==b) {
  }

  if (a<b) {
  }

What else can we do? Well, one improvement is to add another (private) conversion function to an integral type, and thereby disallow the nonsensical operations, even those for equality and ordering. Simply declaring a private conversion function to int does the trick. However, some drawbacks remain, making the solution less than satisfactory. The error messages when a user invokes the ambiguity aren't consistent, or readable. Also, these conversion functions may interfere with perfectly valid conversions and overloads. So we must look elsewhere for a clean solution to this problem.

Not Exactly Obvious, operator!

It's time to move on to safer ground, through operator!. Programmers are already accustomed to using this unary logical negation operator in Boolean contexts, which is a desirable property for intuitive usage. Still, some users might not be ready for what some people call the double-bang trick (see below), which is a requirement for checking the "good state" of such an object. The implementation is trivial:

  bool operator!() const {
    return !ok_;
  }

This is a much better approach—no more implicit conversion or overloading issues to worry about, and two idiomatic ways of testing Testable:

  Testable test;
  if (!!test) 
    std::cout << "Yes, test is working!\n";
  if (!test2) {
    std::cout << "No, test2 is not working!\n";

The first version utilizes a useful trick: if (!!test). It's sometimes called the double-bang trick [1], but alas, it is not nearly as elegant or straightforward as if (test). [Editor's note: This is an old C trick used to map non-zero values to the number 1 so you can have numeric integer values map into a binary-valued index (0 or 1) for use with an array of size two] This is a pity, because if people don't understand how something works it really doesn't matter whether it's safe or not. It's still a very useful technique, but it will typically be used in library code, where �ordinary� users never see it. Of course, it's still possible to compare different types, just as was the case with the first approach (although the obscure syntax should make it obvious that it rarely makes sense to do so). Are there better ways than this?

Page 1 of 3  >>

The C++ Source | C++ Community News | Discuss | Print | Email | First Page | Previous | Next

Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2014 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us