Boost logo

Boost :

From: Fernando Cacciola (fcacciola_at_[hidden])
Date: 2001-09-19 13:08:58


----- Original Message -----
From: Douglas Gregor <gregod_at_[hidden]>
To: <boost_at_[hidden]>
Sent: Monday, September 17, 2001 4:57 PM
Subject: Re: [boost] Re: More on the optional class.

> On Monday 17 September 2001 11:35, you wrote:
> [snip]
> > With respect to the 'operator T*()', I haven't adopted it becuase it
would
> > allow the following:
> >
> > optional<int> opt1 = 1234 ;
> > optional<int> opt2 = 1234 ;
> >
> > if ( opt1 == opt2) // this will compile OK if there is an 'operator
> > T*()', but won't evaluate as expected.
> >
> > the above boolean expression is ill-formed w.r.t the optional
> > specification, but there is no compile-time nor runtime indication of
that.
>
> You can "poison" comparisons between optional<T>'s very easily, however,
like
> this:
>
> template<typename T1, typename T2>
> void operator==(const optional<T1>&, const optional<T>&);
>
> Since the operator == returns void, any attempt to use "opt1 == opt2" will
> generate a compile-time error. In the pointless case that the user writes
the
> (harmless) statement expression "opt1==opt2;", the failure will occur at
link
> time.
>
> > I decided to support only those idioms that would prevent the mistakes
as
> > above. AFAICT, these are (1) operator !(), (2) a friend 'T* get(opt)'
> > function (thanks to Peter Dimov!).
>
> Many of these concerns (how to use an object in a boolean context) were
> discussed prior to the review of Function. I settled on the use of Peter
> Dimov's pointer-to-member-of-useless-class technique for the boolean
> conversion, and on poisoning operator== and operator!= for Function class
> templates. The source file boost/function/function_base.hpp contains the
code
> for both.
>
> The fact that this discussion has come up again hints that these solutions
> should perhaps be documented somewhere. I would suggest the addition of a
> "safebool" type to utility.hpp that assists in the object-to-bool
conversion.
> Similarly, operator poisoning would belong in operators.hpp: for instance,
a
> type "not_equality_comparable" could be derived from to poison operators
==
> and != for the given class.
>
I've been playing with the safe_bool technique -taken from
function_base.hpp- and I've found the following:

I've used the following test bed.

In this test there is a class A with either 'operator bool()' or 'operator
safe_bool()'.
I can switch between operators by commenting out one of them.
Following the class declaration there is a set of test cases. Each test is
contained within one of two macros:
PASSED() or FAILED().
I can switch between macros by commenting out one of them.

With operator bool(), all test passed.
With operator safe_bool(), only the tests that are inside PASSED() in the
code below passed.

Conclusions follow the code.

// Sample class with safe_bool
class A
{
    struct dummy { void nonnull() {}; };
    typedef void (dummy::*safe_bool)();

  public:

    // This isn't operative.
    // We're interested only in the allowed conversions.

    operator safe_bool () const { return &dummy::nonnull; }
     //operator bool() const { return true ; }
} ;

#define PASSED(code) code
#define FAILED(code)

PASSED(
//FAILED(
  void accept_bool ( bool ) {}
  void test_accept_bool() { A a ; accept_bool(a) ; }
)

PASSED(
//FAILED(
  bool give_bool() { A a ; return a ; }
)

PASSED(
//FAILED(
  bool compare_to_literal_zero ()
  {
    A a ;
    return a == 0 ;
  }
)

//PASSED(
FAILED(
  bool compare_to_literal_nonzero ()
  {
    A a ;
    return a == 1 ;
  }
)

PASSED(
//FAILED(
  bool convert_to_bool()
  {
    A a ;
    bool b = a ;
    return b ;
  }
)

//PASSED(
FAILED(
  int convert_to_int()
  {
    A a ;
    int i = a ;
    return i ;
  }
)

//PASSED(
FAILED(
  void accept_int ( int ) {}
  void test_accept_int() { A a ; accept_int(a) ; }
)

//PASSED(
FAILED(
  int give_int() { A a ; return a ; }
)

//PASSED(
FAILED(
  bool compare_to_bool ()
  {
    bool null = 0 ;
    A a ;
    return a == null ;
  }
)

//PASSED(
FAILED(
  bool compare_to_const_bool ()
  {
    const bool null = 0 ;
    A a ;
    return a == null ;
  }
)

//PASSED(
FAILED(
  bool compare_to_int ()
  {
    int null = 0 ;
    A a ;
    return a == null ;
  }
)

PASSED(
file://FAILED(
  bool compare_to_const_int ()
  {
    const int null = 0 ;
    A a ;
    return a == null ;
  }
)

int main()
{
  return 0 ;
}

Conclusions:

As you can see, all conversions to bool were accepted, except the one
required to allow the explicit comparison with a bool value.
Notice that a conventional 'operator bool()' also accepts all the int
conversions because of the allowed promotion from bool to int.

Notice also that the comparison to the literal 0 (not literal 1) and the
comparison to a const int (with value 0) are allowed.

Of course, this is the expected behavior, otherwise the expressions if ( A )
/ if ( A == 0 ) wouldn't compile.

safe_bool is a pointer (to a member function), thus it is allowed to compare
with a null pointer value and it is allowed to decay into bool.

If I change safe_bool from a member function pointer into a regular pointer
(int const*), all the same tests passed except 'compare_with_const_int'
(pretty odd).

It all means that safe_bool is safer than 'operator bool()', but not safer
than 'operator T const*' (when the later is applicable).

If safe_bool is going to be added to utility.hpp I think these results
should be documented.

In the particular case of optional<T>, the allowed conversion to bool
prevents this technique to be applicable.
It seems to me that (!,!!) and get(opt) are still the only truly safe
choices. (and then the poison ==,!= are not required)

Fernando Cacciola
Sierra s.r.l.
fcacciola_at_[hidden]
www.gosierra.com


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