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?