views:

133

answers:

3

I have recently read the safe bool idiom article. I had seen this technique used a few times, but had never understood quite why it works, or exactly why it was necessary (probably like many, I get the gist of it: simply using operator bool () const allowed some implicit type conversion shenanigans, but the details were for me always a bit hazy).

Having read this article, and then looked at a few of its implementations in boost's shared_ptr.hpp, I thought I had a handle on it. But when I went to implement it for some of the classes that we've borrowed and extended or developed over time to help manage working with Windows APIs, I found that my naive implementation fails to work properly (the source compiles, but the usage generates a compile-time error of no valid conversion found).

Boost's implementations are littered with conditions for various compilers level of support for C++. From using the naive operator bool () const, to using a pointer to member function, to using a pointer to member data. From what I gather, pointer to member data is the most efficient for compilers to handle IFF they handle it at all.

I'm using MS VS 2008 (MSVC++9). And below is a couple of implementations I've tried. Each of them results in Ambiguous user-defined-conversion or no operator found.

template<typename HandlePolicy>
class AutoHandleTemplate
{
public :
    typedef typename HandlePolicy::handle_t handle_t;
    typedef AutoHandleTemplate<HandlePolicy> this_type;
    {details omitted}
    handle_t get() const { return m_handle; }
    operator handle_t () const { return m_handle; }

#if defined(NAIVE)
    // The naive implementation does compile (and run) successfully    
    operator bool () const { return m_handle != HandlePolicy::InvalidHandleValue(); }
    bool operator ! () const { return m_handle == HandlePolicy::InvalidHandleValue(); }
#elif defined(FUNC_PTR)    
    // handle intrinsic conversion to testable bool using unspecified_bool technique
    typedef handle_t (this_type::*unspecified_bool_type)() const;
    operator unspecified_bool_type() const // never throws
    {
     return m_handle != HandlePolicy::InvalidHandleValue() ? &this_type::get() : NULL;
    }
#elif defined(DATA_PTR)

    typedef handle_t this_type::*unspecified_bool_type;
    operator unspecified_bool_type() const // never throws
    {
     return m_handle != HandlePolicy::InvalidHandleValue() ? &this_type::m_handle : NULL;
    }
#endif
private :
    handle_t m_handle;
{details omitted}
};

And here's a snippet of code that either works (naive implementation), or errors (either of the unspecified_bool techniques, above):

// hModule is an AutoHandleTemplate<ModuleHandlePolicy>
if (!hModule)

and:

if (hModule)

I have already tried enabling the operator! in all cases - but although the first case then works, the second fails to compile (ambiguous).

This class seems to me to be so very like a smart_ptr (or auto_ptr). It should support implicit conversion to its underlying handle type (HMODULE) in this case, but it should also handle if (instance) and if (!instance). But if I define both the operator handle_t and the unspecified_bool technique, I get errors.

Can someone please explain to me why that is so, and perhaps suggest a better approach? (or should I be content with the naive approach, at least until C++0x is complete and explicit operators are implemented in my compiler)?

EDIT:

It seems that the answer may well be that if I define an implicit conversion to an integral, that C++ will use that conversion for any if (instance) type expressions. And that, at least for the above class, the only reason to define any other operators (operator bool) is to explicitly override using the implicit integral conversion to something else (in the above case, forcing it to be a comparison to INVALID_HANDLE_VALUE instead of the implicit NULL).

And using the unspecified_bool technique only really makes sense when you're not providing an integral conversion operator?

A: 

The ambiguity comes from having two possible conversion operators; either:

operator handle_t () const;
operator unspecified_bool_type() const;

or:

operator handle_t () const;
operator bool () const;

Both can be used in a boolean expression, so you have ambiguity.

MSN
At least with VC++ 2008, you can both have an operator handle_t() and operator bool(). if (handle) resolves to the operator bool, while fn(handle) where fn -> void fn(handle_t) resolves to operator handle_t. Whether that's fully standards compliant behavior I don't know.
Mordachai
Right. In the second case, it's not ambiguous. I think the first one is still ambiguous, however.
MSN
+1  A: 
AutoHandleTemplate<ModuleHandlePolicy> hModule( ... );
HMODULE raw_handle = hModule; // if we want to this line works,
// AutoHandleTemplate<ModuleHandlePolicy> should \
//    be implicitly converted to it's raw handle type - HMODULE.

If one smart-ptr can implicitly converted to it's raw handle type and the raw handle type could be used in a boolean test itself, like :

HMODULE the_raw_handle = ...;
if ( the_raw_handle ) {}  // this line is ok

For those smart-ptrs, there is no need (and should not) to define conversions to bool,void* or safe_bool, otherwise, ambiguity.

operator bool(), void*(), safe_bool() are used for the smart-ptrs which could not be implicitly convert to it's raw handle or it's raw handle couldn't used in a boolean context.

Try this code :

template<typename HandlePolicy>
class AutoHandleTemplate
{
public :
      typedef typename HandlePolicy::handle_t handle_t;
      typedef AutoHandleTemplate<HandlePolicy> this_type;
      {details omitted}

      operator handle_t () const {
            return m_handle==HandlePolicy::InvalidHandleValue()? 0: m_handle;
      }

      // no more conversion functions

private :
      handle_t m_handle;
      {details omitted}
};
OwnWaterloo
d'oh! I never thought to have the handle_t do such a check and normalize its output like such. Its possible that there are pitfalls in such an approach, just because zero could be a valid handle value, and the conversion to handle now can hand off an invalid handle as a possibly valid one (i.e. fn(handle) can now be handed a zero-handle for what is really an invalid handle). That's not the worlds worst, given that real programs need to have already tested if (handle) fn(handle) or similar.
Mordachai
Something wrong in my statement. operator bool() could avoid ambiguity.If ( handle ), handle -> handle.operator bool(), once; handle.operator void*(), void* -> bool, twice, handle->safe_bool, safe_bool -> bool, twice. So handle.operator bool() is suit forboth implicit conversion and boolean test.
OwnWaterloo
So one way may be using operator bool() and be careful of `hModule << 1`,`int i = hModule` ... The other may be AutoHandleTemplate<HMODULE,NULL> NULL_unacceptable;AutoHandleTemplate<HMODULE,INVALID_HANDLE_VALUE> INVALID_HANDLE_VALUE_unacceptable;
OwnWaterloo
A: 

All the idioms suck, really.

The best solution is:

1) don't have any implicit conversion operators

2) have an operator! override with bool return type. Yes, this means that some test might need to be written as if(!!myObject), but that's a small price to pay.

DrPizza
I'm excited to see that C++ 0x will have explicit operators. It seems like one of those things that needs to be cleaned up by the language itself. Its clearer in some ways to have to say if (thing.IsValid()) or if (thing.NotNull()) etc., but its extremely taxing for generic code to have to have special knowledge (or a traits template) in order to handle the "how do I test a T" logic. There is something very elegant about if (p) ... where p is any pointer-like or integral type. Its worth finding a way to accomplish, IMO.
Mordachai