Boost logo

Boost :

From: Richard Smith (richard_at_[hidden])
Date: 2006-10-23 13:30:20


I was recently bitten by bug in some code using
boost::optional. A simple test case is:

  #include <boost/optional.hpp>
  #include <cassert>

  int main() {
    boost::optional<int> i = 0;
    assert( i == 0 );
  }

Somebody had written code like this, presumably assuming the
second argument to the comparison operator would convert
implicitly to a boost::optional<int> and the code would
behave as if it read:

  assert( i && i.get() == 0 );

Unfortunately, although the code compiles, overload
resolution finds an additional undocumented operator:

  bool boost::operator==<int>( boost::optional<int> const&,
                               boost::none_t const& )

This is selected because none_t is a pointer-to-member type
and 0 is a valid null pointer-to-member literal, meaning the
code is equivalent to

  assert( not i );

which asserts. Even in the absense of this operator, the
comparison preferetially goes via i's conversion to
unspecified-bool-type (again, implemented as a
pointer-to-member function) and the code asserts. (In both
cases, if '0' is replaced by '1' -- no longer a null pointer
literal -- the code fails at compile time.)

In this respect, boost::optional is the same as, say,
boost::shared_ptr, in that C++'s lack of less conversion-
prone boolean type can sometimes cause odd effects.
However, where boost::optional has value-semantics, is
implicitly constructible from T, and has 'deep' comparision
semantics, unlike boost::shared_ptr, users are much more
likely accidently to write code that is buggy because of
this.

I've been trying to think of a way of triggering a
compile-time error in this case, but I haven't been able to
think of anything wholly satisfactory. The best way I can
do is to have comparison operators that trigger a
BOOST_STATIC_ASSERT:

  template <class T, class NullPointerType>
  typename boost::enable_if<
    boost::is_integral< NullPointerType >, bool
>::type
  operator==( boost::optional<T> const& x,
              NullPointerType y ) {
    BOOST_STATIC_ASSERT( false );
  }

(and similarly for the other comparisons, and with the
arguments transposed). This could be wrapped up into a
'safe_bool_conversion' base class:

  // Implement a safe boolen conversion using operator!
  template <class Derived>
  class safe_bool_conversion
  {
    void dummy_fn() const {}
    typedef void (safe_bool_conversion::* bool_t)() const;

  public:
    operator bool_t() const {
      return !!static_cast<Derived const&>(*this);
    }

    // Plus the various asserting operators
  };

  template <class T>
  class optional
    : public safe_bool_conversion< optional<T> >
  {
  };

An alternative (for boost::optional) would be to simply
create valid operators for each of these comparisons so that
the original code does the expected thing.

  template <typename T, typename U>
  inline typename boost::enable_if<
    boost::is_convertible<U,T>, bool
>::type
  operator==( boost::optional<T> const& x, U const& y ) {
    return x && x.get() == y;
  }

... and so on.

Does this sound worth persuing?

Richard Smith


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk