The Artima Developer Community
Sponsored Link

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

<<  Page 3 of 3

Advertisement

A Reusable Solution

If you're like me, you don't want to follow the aforementioned steps every time you need to make a class �Boolean testable�. You want something reusable, and you deserve no less! 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), I add support for both versions below:
  class safe_bool_base {
  protected:
    typedef void (safe_bool_base::*bool_type)() const;
    void this_type_does_not_support_comparisons() const {}

    safe_bool_base() {}
    safe_bool_base(const safe_bool_base&) {}
    safe_bool_base& operator=(const safe_bool_base&) {return *this;}
    ~safe_bool_base() {}
  };

  template <typename T=void> class safe_bool : public safe_bool_base {
  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() {}
  };

  template<> class safe_bool<void> : public safe_bool_base {
  public:
    operator bool_type() const {
      return boolean_test()==true ? 
        &safe_bool_base::this_type_does_not_support_comparisons : 0;
    }
  protected:
    virtual bool boolean_test() const=0;
    virtual ~safe_bool() {}
  };

  template <typename T, typename U> 
    void 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> 
  void 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:

  class Testable_with_virtual : public safe_bool<> {
  protected:
    bool boolean_test() const {
      // Perform Boolean logic here
    }
  };

  class Testable_without_virtual : 
    public safe_bool <Testable_without_virtual> {
  public:
    bool boolean_test() const {
      // Perform Boolean logic here
    }
  };

The first class, Testable_with_virtual, derives publicly from safe_bool, and implements a virtual function boolean_test—this function is called whenever an instance is tested (as in if (obj){}, or if (!obj){}). The second class, Testable_without_virtual, also derives publicly from safe_bool, and in addition, it passes itself as a template parameter to its base class. This little trick—known as the Curiously Recurring Template Pattern— enables the base class to downcast (to the derived class) using static_cast and call boolean_test with no extra runtime overhead and no virtual function calls. Some people may feel that this is a slight misuse of inheritance; while it might be argued that an instance of a derived class is-a safe_bool of sorts, this is certainly not the intent of this code. However, there is little reason to believe that even neophyte programmers will fall into the trap of misunderstanding this relationship. The destructors of the safe_bool classes are protected to minimize the potential for misuse. But there's still hope for the (in my opinion, overly) conscientious object-oriented purist; use private inheritance, and make the conversion function public by reintroducing it in the correct scope:

  class Testable_without_virtual : 
    private safe_bool <Testable_without_virtual> {
  public:
    using safe_bool<Testable_without_virtual>::operator safe_bool;

    bool boolean_test() const {
      return true; // Logic goes here!
    }
  };

Matthew Wilson [5] pointed out that the inheritance strategy (using safe_bool as a base class) may lead to size penalties on some compilers, specifically, those that do not implement EBO (Empty Base Optimization) properly. Although most modern compilers do when it comes to single inheritance, there may be a size penalty with multiple inheritance.

Knowing When to Say No

Yes, this is a cool idiom, and you're probably eager to try it out on some of your own classes, right? Before you go ahead, please consider that it's imperative to understand that this idiom should only be used where there is a reasonably unambiguous notion of validity for objects of a class. Consider the void* conversion in iostreams. Do you know which state flags are considered in that test? Most people probably think they do. I looked it up and realized that at least I was wrong—the eofbit flag is ignored. This goes to show that if programmers' expectations of the semantics may differ, providing a member function with a descriptive name is much better. In the case of iostreams, it's reasonable to test for errors using fail(), and failure of input using !good(). For a container class, having a conversion function would be absolutely disastrous, because the possible interpretations of its meaning are so plentiful. For most classes, the proper way to design tests for validity is to provide member functions with clever names, not clever conversion functions. There you go.

Prior Art

New discoveries typically build on previous findings, and the safe bool idiom is no different; in addition to existing protocols with similar properties, related topics have been thoroughly treated in books and articles. The following is by no means an exhaustive listing, but rather a small collection of important contributions that I've come across [6].

Without the work of these people, and others who've come to the same conclusions, chances are that the safe bool idiom would not have seen the light of day.

Summary

As C++ programmers, we accept that there are a number of different ways to reach a programming goal—and different approaches typically involve different tradeoffs. Carefully balancing usability and safety is hard, and depending on which side of the fence you're standing on, you'll either be screaming "safety", or "usability" first. It's therefore especially satisfying to be able to present a solution to a common problem that appeals to both sides. As usual, I would have liked to say that this was my idea—but, also as usual, that is not the case. The man behind this ingenious idea is Peter Dimov [7,8], and it's to him we should send our thanks�as users, and as library developers.

Acknowledgements

I'd like to thank the following people for their gracious help and suggestions with this article:

References

  1. Some people also refer to it as �the Boolean conversion operator�.
  2. ISO/IEC 14882:98, �5.3.5/2.
  3. See Vandevoorde and Josuttis, C++ Templates, Addison-Wesley, 2003, pp. 392-393.
  4. If the tests are indeed valid, you can always enable them by defining the operators for your class.
  5. Don't miss Matthew's forthcoming book, Imperfect C++: Practical Solutions For Real-life Programming [Addison-Wesley 2004], where this topic and many more are covered in great detail!
  6. If you're aware of other important contributions, please let me know!
  7. More about Peter at http://www.boost.org/people/peter_dimov.htm.
  8. The twist of declaring (but not defining) operator== and operator!= was invented by Douglas Gregor, and the technique is sometimes referred to as �poisoning operators�. I've added an (parameterized) implementation that fails to compile (when instantiated) rather than just defining the operators and waiting until link time for the error to appear.

Talk Back!

Discuss this article in the Articles Forum topic, The Safe Bool Idiom.

About the Author

Bjorn Karlsson is proud to be a C++ designer, programmer, teacher, preacher, and student. He has finally learned enough about C++ to realize how little he knows. When not reading or writing articles, books, or code, he has the privilege to be a part of the Boost community, and a member of The C++ Source Advisory Board. He appreciates it when people send him interesting emails at bjorn.karlsson@readsoft.com.

<<  Page 3 of 3


Sponsored Links



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