From: Ed Brey (brey_at_[hidden])
Date: 2003-09-08 08:51:58


Douglas Gregor wrote:
> On Friday 05 September 2003 02:43 pm, Ed Brey wrote:
>> struct inert_bool_structure {};
>> typedef inert_bool_structure const* inert_bool;
>
> This allows several things we'd rather not allow:
> shared_ptr<T> p;
> p + 2; // not quite what we expected
> delete p; // dangerous
     function_obj == p // bool comparison may be unexpected.
     void const* v = p

These are all well-observed imperfections. (I tacked on the last two myself, to get the issues in one place.)

In the "p + 2" case, it would be better if the code didn't compile; OTOH, the results don't break from boolean semantics, so doesn't seem likely to confuse anyone.

The "delete p" case is fixed with a private destructor, i.e.:
struct inert_bool_structure { private: ~inert_bool_structure(); };

In the equality between different classes case, we enter mixed metaphor land. By one school of reasoning, if you can say:

if (function_obj) ...
and
if (shared_ptr_obj) ...
and
if (function_obj && shared_ptr_obj) ...

then wouldn't

if (function_obj == shared_ptr_obj) ...

be a logical extension? A counter argument is that this isn't the only way to think of the scenario, and a coding error can occur when someone is quite reasonable thinking in terms of content equality.

The void const* conversion case could hide a nasty bug, since the meaning subtly changes in shared_ptr by forgetting the .get(). No solution comes to mind for this.

The problems with inert_bool seem great enough to kill it. It is a shame in a way, since the efficiency problem of the pointer to member approach affects each usage, even though the added safety comes in handy only rarely.

Unless I missed something bool_testable only provides half the problem: it provides operator!, but not the implicit bool conversion. Perhaps the thinking is to add a templated bool conversion. This would solve the heterogeneous class comparison problem, but not the void const* problem. Still, maybe it is the best compromise.

An entirely different approach is to use a null class.

struct null_t {
    template<typename T> operator T*() const {return 0;}
};
null_t const null;

template<typename T> inline bool operator==(shared_ptr<T> const& p, null_t) {return p.get() == 0;}
template<typename T> inline bool operator==(null_t, shared_ptr<T> const& p) {return p.get() == 0;}
template<typename T> inline bool operator!=(shared_ptr<T> const& p, null_t) {return p.get() != 0;}
template<typename T> inline bool operator!=(null_t, shared_ptr<T> const& p) {return p.get() != 0;}

In this approach, shared_ptr wouldn't include any boolean operator at all. Instead, code using would look like this:

if (p != null) ...

Not as nice an terse as just "if (p)", but safer and more explicit, and still prettier, IMHO, than "if (p.get())". A side benefit to this approach is that a null object would be a nice utility addition to boost anyway, since it is less ambiguous than 0 (although it doesn't work on pointer to members ... ah those pesky tradeoffs).

Further thoughts?

Ed