In C++/CX, hat pointers are contextually convertible to bool, but you can’t always static_cast them to bool

Raymond Chen

Raymond

C++/CX is a language extension intended to make consuming the Windows Runtime easier. It is, however, no longer the C++ projection of choice. That honor now belongs to C++/WinRT, which allows you to consume the Windows Runtime using standard-conforming C++, no language extensions required.

For those of you stuck with C++/CX, here’s a little puzzle: What do these functions do?

bool Mystery1(Object^ o)
{
    if (o) {
        return true;
    } else {
        return false;
    }
}

bool Mystery2(Object^ o)
{
    return static_cast<bool>(o);
}

bool Mystery3(Object^ o)
{
    return bool(o);
}

bool Mystery4(Object^ o)
{
    return (bool)o;
}

You’d think these would all be equivalent, but they’re not.

In the first mystery function, the hat pointer o is contextually converted to bool, and that’s done by treating nullptr as falsy and anything else as truthy. In this respect, hat pointers are like star pointers.

The remaining mystery functions take the object that o points to and attempt to unbox it to a bool, and they all behave the same way:

If o isThen you get
(Object^)truetrue
(Object^)falsefalse
nullptrNullReferenceException thrown
anything elseInvalidCastException thrown

If you just want to know what happens and don’t care to understand the deep metaphysical significance of those last two rows, I don’t blame you.

But that’s probably not why you’re here. You want to understand the weird crazy world that led to the strange table above.

What’s going on is that a Object^ is really an IInspectable* under the hood. And cast operations on IInspectable* are performed by doing a Query­Interface. In this case, we are casting to IBox<bool>*.

If you have a nullptr, then the attempt to call Query­Interface results in a null pointer dereference, hence the Null­Reference­Exception.

If the object is not a boxed bool, then the Query­Interface fails with E_NO­INTERFACE, which is expressed in C++/CX as an Invalid­Cast­Exception.

For me, the weird part is that there are two different categories of results: The contextual conversion is different from the other conversions.

It means that you get weird puzzles like this:

Object^ p = false;
Object^ q = false;

if (p)                    std::cout << 1;
if ((bool)p)              std::cout << 2;
if (static_cast<bool>(p)) std::cout << 3;
if (p == q)               std::cout << 4;
if (p == false)           std::cout << 5;
if (!p)                   std::cout << 6;
if ((bool)p == (bool)q)   std::cout << 7;

What does this fragment print?

ConditionWhat’s happeningResult
if (p)Tests p against nullptr.prints 1
if ((bool)p)Unboxes p to bool.does not print
if (static_cast<bool>(p))Unboxes p to bool.does not print
if (p == q)Compares two objects for identity.does not print
if (p == false)Boxes false then compares two objects for identity.does not print
if (!p)Tests p against nullptr.does not print
if ((bool)p == (bool)q)Unboxes p and q and compares them.prints 7

Converting hat pointers to bool is very strange. Be glad you don’t have to deal with it.

Next time, we’ll look at C++/WinRT. It’ll be a lot less strange.

4 comments

Comments are closed.

    • Avatar
      Tautvydas Žilys

      The types just get inflated in the headers. IReference int becomes (taken from Windows.Foundation.h):

      typedef struct __FIReference_1_intVtbl
      {
      BEGIN_INTERFACE
      HRESULT ( STDMETHODCALLTYPE *QueryInterface )(__RPC__in __FIReference_1_int * This,
      /* [in] */ __RPC__in REFIID riid,
      /* [annotation][iid_is][out] */
      _COM_Outptr_ void **ppvObject);
      ULONG ( STDMETHODCALLTYPE *AddRef )( __RPC__in __FIReference_1_int * This );
      ULONG ( STDMETHODCALLTYPE *Release )( __RPC__in __FIReference_1_int * This );

      HRESULT ( STDMETHODCALLTYPE *GetIids )( __RPC__in __FIReference_1_int * This,
      /* [out] */ __RPC__out ULONG *iidCount,
      /* [size_is][size_is][out] */ __RPC__deref_out_ecount_full_opt(*iidCount) IID **iids);
      HRESULT ( STDMETHODCALLTYPE *GetRuntimeClassName )( __RPC__in __FIReference_1_int * This, /* [out] */ __RPC__deref_out_opt HSTRING *className);
      HRESULT ( STDMETHODCALLTYPE *GetTrustLevel )( __RPC__in __FIReference_1_int * This, /* [out] */ __RPC__out TrustLevel *trustLevel);

      /* [propget] */ HRESULT ( STDMETHODCALLTYPE *get_Value )(__RPC__in __FIReference_1_int * This, /* [retval][out] */ __RPC__out int *value);
      END_INTERFACE
      } __FIReference_1_intVtbl;

      interface __FIReference_1_int
      {
      CONST_VTBL struct __FIReference_1_intVtbl *lpVtbl;
      };

      Each generic interface has “base IID” (or w/e it’s called), and then the actual inflation IID can be calculated algorithm described here: https://docs.microsoft.com/en-us/uwp/winrt-cref/winrt-type-system

  • Ian Yates
    Ian Yates

    Reminds me a bit of JavaScript

    Nice way to get that contextual conversion, which I suspect would work here, is
    var isTrue ==!!o;

    (disclaimer, typed on a phone keyboard fighting autocorrect, and thus definitely not tested)