|
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