Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 24.  operator bool()


24.7. Operating in the Real World

Of course, when I said that compilers have improved to allow for the operator int boolean::*() const technique, they've not all come along in unison. As is always the case in the real world, there are significant differences to the support for such complex techniques.[2] For example, some cannot handle use of the operator in compound expressions, as in:

[2] Visual C++ 6.0 does not support this, nor does Borland (not even the latest 5.6.4). Interestingly, Watcom's compiler manages more support than Borland.



ExpressibleThing  et1;


ExpressibleThing  et2;





if(et1 && et2) // Error 'operator &&' not implemented for Ex ...


{


  . . .



The scariest behavior is of Visual C++ 6, which compiles all expressions involving this operator without a problem, but misinterprets the truth of (sub-)expressions at run time!

So although the vast bulk of the modern compilers handle the optimal technique with aplomb, if you care about portability, you must consider a mixed solution:

Listing 24.5.


private:


  struct boolean { int i; private: void operator delete(void*); };


public:


#ifdef ACMELIB_OPERATOR_BOOL_AS_OPERATOR_POINTER_TO_MEMBER_SUPPORT


  operator int boolean::*() const


  {


    return m_b ? &boolean::i : NULL;


  }


#else


  operator Boolean const*() const


  {


    boolean b;


    return m_b ? &b : NULL;


  }


#endif ACMELIB_OPERATOR_BOOL_AS_OPERATOR_POINTER_TO_MEMBER_SUPPORT



But I'm sure you'd be as unhappy as me to see that stuff cluttering up all your lovely succinct classes. There was another of those think-about-it-for-several-days efforts to come up with the solution, but I think you'll agree that the result is very nice indeed. Let's first look at it in action:



private:


  typedef operator_bool_generator<class_type>   boolean_type;


public:


  operator boolean_type::return_type() const


  {


    return boolean_type::translate(m_b);


  }



The actual operator type, and the mechanism of providing the "true" and "not true" values is provided by the operator_bool_generator template:

Listing 24.6.


template <typename T>


struct operator_bool_generator


{


public:


  typedef operator_bool_generator<T>  class_type;


#ifdef ACMELIB_OPERATOR_BOOL_AS_OPERATOR_POINTER_TO_MEMBER_SUPPORT


  typedef int class_type::*return_type;


  /// Returns the value representing the true condition


  static return_type true_value()


  {


    return &class_type::i;


  }


private:


  int i;


#else


  typedef class_type const  *return_type;


  /// Returns the value representing the true condition


  static return_type true_value()


  {


    class_type  t;


    void    *p = static_cast<void*>(&t);


    return static_cast<return_type>(p);


  }


#endif // ACMELIB_OPERATOR_BOOL_AS_OPERATOR_POINTER_TO_MEMBER_SUPPORT


public:


  /// Returns the value representing the false condition


  static return_type false_value()


  {


    return static_cast<return_type>(0);


  }


  /// Does the ternary operator for you


#ifdef ACMELIB_MEMBER_TEMPLATE_FUNCTION_SUPPORT


  template <typename U>


  static return_type translate(U b)


#else /* ? ACMELIB_MEMBER_TEMPLATE_FUNCTION_SUPPORT */


  static return_type translate(bool b)


#endif // ACMELIB_MEMBER_TEMPLATE_FUNCTION_SUPPORT


  {


    return b ? true_value() : false_value();


  }


private:


  void operator delete(void*);


};



Now compilers that support operator int boolean::*() const and those that merely support operator Boolean const*() const are catered for equally well by the operator_bool_generator template. It might look like a lot of guff, but all the nasty conditional compilation is hidden inside the template, rather than polluting your classes. And it's perfectly efficient: compilers easily optimize it all out to nothing but the fundamental Boolean test passed to translate().

The template parameter is merely there to give the operator's return type uniqueness. The example uses the enclosing class's member type class_type (section 18.5.3), as that's my habit, but you could use the name of the enclosing class itself if you prefer. In fact, it could just as easily be an inner class, or any other class, but using the enclosing class does not require any additional type definitions.

Alas, that's not quite the final picture, nice as it is. Visual C++ (from versions 4.2 right up to the latest 7.1) has a peculiarity whereby it complains that it is illegal to have a typedef between the operator keyword and the operator type. Huh? Indeed, you may ask. Clearly it's a paradox inside an enigma, cleverly disguised as a bug, wrapped in a feature. Similarly, Borland gets discombobulated when an operator is defined with the scoping operator :: in the operator type.

The workaround for Visual C++—don't ask me to explain it—is to use some aspect of the class before the operator. The following does it:



private:


  typedef operator_bool_generator<class_type>   boolean_type;


  typedef boolean_type::return_type             operator_bool_type;


public:


  operator boolean_type::return_type() const


  {


    . . .



I was a bit glum about this for a while as it had uglified my solution. Thankfully a good night's sleep was the answer, and I realized that one way of declaring and using the type in one statement was to define the enclosing class's boolean_type from the instantiation of operator_bool_generator's own class_type, so it boils down to:



private:


  typedef operator_bool_generator<class_type>::class_type   boolean_type;


public:


  operator boolean_type::return_type() const


  {


    . . .



Alas, for Borland, this still doesn't cut it. When the enclosing type is a template itself, using its instantiation type—class_type—does not work, and we're forced to define the operator return type external to the operator itself, as in:

Listing 24.7.


private:


  typedef typename


   operator_bool_generator<class_type>::class_type


                                  operator_bool_generator_type;


  typedef typename operator_bool_generator_type::return_type


                                  operator_bool_type;


public:


  operator operator_bool_type() const


  {


    return operator_bool_generator_type::translate(. . .



So, after all that, a macro is needed, which encapsulates the above typedefs, and looks like the following:



private:


  DEFINE_OPERATOR_BOOL_TYPES(class_type, bool_gen_type, bool_type);


public:


  operator bool_type() const


  {


    return bool_gen_type::translate(. . .



Actually, there are two macros to cater for the times when the operator is defined inside a template, in which case the typename keyword is needed for a type specifier, or not, in which case it's not. Examples of these two macros, DEFINE_OPERATOR_BOOL_TYPES() and DEFINE_OPERATOR_BOOL_TYPES_T(), are found on the CD.

And that's it! It's been a lot of effort for something so conceptually simple, but I hope you'll agree that having a maximally safe, unified technique for implementing a Boolean operator is worth it. We can now provide an operator that exhibits the required semantics in conditional expressions of arbitrary complexity, suffers from none of the dangerous implicit conversions that are evinced in the other forms, and is portable to a very wide variety of compilers with the maximal safety achievable with each.


      Previous section   Next section