A Classy Type System for Modern C++

C++11 changed the game for C++ developers. We no longer need to defend the fact that C++ is so much harder to use than those other “modern” languages like C#. Because it isn’t. We no longer need to make a choice between performance and productivity. Because they’re not mutually exclusive. Standard C++ gives you both and Modern C++ brings it to the Windows Runtime.

Consider a simple C# example to determine the capabilities of any connected mice:

MouseCapabilities caps = new MouseCapabilities();

if (0 < caps.MousePresent)
{
    Debug.WriteLine("Buttons " + caps.NumberOfButtons);
}

I need to use the C# new operator to create a MouseCapabilities object and call its constructor. I can then use the various members to figure out what I can expect for my app’s input. The MousePresent property is intended to indicate whether a mouse is present, hence its name, but it can also be used (rarely) to determine how many mice are detected. As such, it actually returns an integer rather than a Boolean value and C# won’t implicitly convert an int to a bool. How well this works in practice is another story.

So why did I start with a C# example? The Windows Runtime was first and foremost designed with the CLR in mind. So much of the Windows Runtime architecture is the way it is purely to make it easier for the CLR even at the expense of language projections that don’t rely on this managed runtime. Still, the Windows Runtime is built on the foundations of COM using native code so Standard C++ has no difficulty going head to head with C#. Here’s the same example but this time using Standard C++:

MouseCapabilities caps;

if (caps.MousePresent())
{
    printf("Buttons %d\n", caps.NumberOfButtons());
}

This is in fact simpler than the C# projection of the same code. A new-expression is not required since that has a different meaning in C++. The C++ compiler is also happy to convert from int to bool for the sake of defining the if-statement’s condition. The only obvious difference is that Standard C++ doesn’t provide properties so those are projected as methods.

What if you need a variable for some class but you want to delay its construction. I can imagine needing a member variable but wanting to actually create it at some later stage. No problem, that’s what a nullptr is for:

MouseCapabilities caps = nullptr;
// some time later ...
caps = MouseCapabilities();

if (caps.MousePresent())
{
    printf("Buttons %d\n", caps.NumberOfButtons());
}

This is in fact very similar to how it appears in C#:

MouseCapabilities caps = null;
// some time later ...
caps = new MouseCapabilities();

if (0 < caps.MousePresent)
{
    Debug.WriteLine("Buttons " + caps.NumberOfButtons);
}

The Standard C++ examples above are using Modern C++ for the Windows Runtime, a Standard C++ language projection. But this is not what Visual C++ provides. Visual C++ offers C++/CX and at first it might seem quite convenient. Here’s the example again but this time using C++/CX:

MouseCapabilities caps;

if (caps.MousePresent)
{
    printf("Buttons %d\n", caps.NumberOfButtons);
}

C++/CX is not Standard C++ and it takes the liberty to add support for properties. Other than that, it appears much the same. Of course, if I want to delay construction of the caps variable then things quickly become more complicated. I cannot simply use a nullptr:

MouseCapabilities caps = nullptr; // error C2440

The C++/CX compiler mistakenly believes that MouseCapabilities actually resides on the stack and therefore a nullptr cannot possibly be converted to a MouseCapabilities object. It demands that you stray even further away from Standard C++ and use a weird little hat:

MouseCapabilities ^ caps = nullptr;

What does the hat do? Well nothing. It’s merely there to satisfy the compiler. It doesn’t change the nature of the MouseCapabilities class or how it is constructed. But now the rest of the example doesn’t compile either:

if (caps.MousePresent) // error C2228
{
    printf("Buttons %d\n", caps.NumberOfButtons); // error C2228
}

The C++/CX compiler now treats the caps variable as a pointer and helpfully suggests that I use ‘->’ instead and I lose all semblance of modernity:

MouseCapabilities ^ caps = nullptr;
// some time later ...
caps = ref new MouseCapabilities();

if (caps->MousePresent)
{
    printf("Buttons %d\n", caps->NumberOfButtons);
}

There are hats, another non-standard new expression, and the dreaded “member of a pointer” operator. It is no wonder that developers prefer to use C#.

Now let’s consider a slightly more interesting example that highlights the Windows Runtime’s classy type system. While COM and the Windows Runtime share a common foundation, COM is really about interfaces while the Windows Runtime is all about classes. Let’s turn back to Standard C++:

Uri first(L"https://kennykerr.ca/");

That’s an object representing a URI value with various properties and methods for parsing and manipulating such strings. I can combine this URI with another to produce a new Uri object:

Uri combined = first.CombineUri(L"articles");

And I can get its canonical string representation:

String string = combined.ToString();

assert(string == L"https://kennykerr.ca/articles");

I won’t bore you with more examples of C++/CX. Needless to say, this example would require more hats and more pointer semantics.

Modern C++ for the Windows Runtime preserves the classy type system that the original component author had imagined while ensuring that the representation at run time is nothing more than the underlying ABI interfaces. The author of the Uri class intended that developers should be able to call the ToString method as if it were a method of the Uri class, but under the covers it’s actually a method on a separate interface. Of course, with Standard C++ the developer is always in control. If you need to trade a bit of convenience for performance and want complete control you can simply retrieve the IStringable interface yourself:

IStringable stringable = combined; // QueryInterface

String string = stringable.ToString();

At this point I have an actual IStringable interface pointer at run time and the cost of calling ToString is precisely what you would expect and without the risk of a lazy call to QueryInterface that might throw off your performance targets.

What does C++/CX offer? A really big hammer called reinterpret_cast. You’re left scrambling for a smart pointer class and the language projection is gone entirely. By contrast Standard C++, thanks to Modern C++ for the Windows Runtime, even lets you reach down to the underlying ABI if you really want but you’re not on your own and the library will continue to support you every step of the way for things like reference counting and additional type support.

Given a Uri object, I can call modern projected methods as well as ABI methods interchangeably without needing to reinterpret_cast and I don’t have to think about reference counting:

Uri uri(L"https://kennykerr.ca/");

int port = uri.Port();

HRESULT hr = uri->get_Port(&port);

The world needs one C++. One Microsoft needs One C++. Modern C++ for the Window Runtime enables Standard C++ to be on par with C# for productivity and provides the unparalleled power and flexibility that C++ developers have come to expect.

9 thoughts on “A Classy Type System for Modern C++

  1. Naveen (@ernaveenverma)

    Thanks for an interesting article. I am still a bit confused. As per my understanding the biggest reason for using C++\CX is to have language projections for C#, JavaScript and VC++ as well. If I am developing a Windows Phone application in C# and XAML which need to interact with a core API which is in C++. The one way is to wrap it with C++\CX which will create a projection for C# and we can use them naturally. Also if needed we can generate the projection for JS and use in JS application. Even though I agree dealing with ref pointers in not as good as standard c++ but the Visual Studio does a good job in creating projection, so we don’t need to deal with complex GUID for accessing the C++ class from C# etc as we were doing earlier with COM interfaces.
    Can we handle the same scenario using modern c++? Would it be possible for you to give a small example?

    Reply
    1. Kenny Kerr Post author

      Modern C++ allows you to write your entire app in Standard C++. There is then no need to use C# for the frontend, C++/CX for interop, and Standard C++ for platform APIs or other backend libraries. You can simply use Standard C++ throughout and avoid all kinds of performance, distribution, and interop challenges.

      Reply
      1. Naveen (@ernaveenverma)

        Aha ok, that would be interesting to try. Performance wise in my experience have not notice a huge difference in C++ and C# app. C# CLR is also very optimized now days. But yes if we can get rid of interop challenges that would be quit good in itself.

  2. arnemertz

    `MouseCapabilities caps = nullptr;` Will work in standard C++ only if `MouseCapabilities` has a constructor that accepts any pointer type. It will not delay the construction of that variable to later but it will construct it in whatever way that constructor is implemented.

    The later line `caps = MouseCapabilities();` will construct a second, temporary object of type `MouseCapabilities` and assign it to the variable. So the semantics is completely different to what you would have in C# with the new expression.

    The C++14 equivalent would be something like this:
    unique_ptr caps = nullptr;
    caps = make_unique();

    Reply
    1. Kenny Kerr Post author

      What may not be obvious is that MouseCapabilities plays the role of the smart pointer and provides a std::nullptr_t constructor so there’s no need to wrap the MouseCapabilities inside some other smart pointer.

      Reply
      1. Joren

        I was confused by this as well, not knowing anything about MouseCapabilities (or the Windows API for that matter). Nevertheless, the statement that construction is delayed by assigning nullptr is simply false.

        Another thing that struck me was the statement that “Standard C++ doesn’t provide properties”. I must admit that I’m not very familiar with C# so there might be more to the concept of properties that I’m not aware of. However, there is nothing in C++ that prohibits a class-designer from making data directly accessible to the outside world. The syntax “if (caps.MousePresent)” is perfectly legal in C++. Of course, any well-designed library won’t expose its data directly in this way, but this is irrelevant.

      2. Kenny Kerr Post author

        Given that you don’t know anything about the Windows API I can see how this might be confusing. Constructing a WinRT class requires a call to a system function, typically RoActivateInstance or RoGetActivationFactory, and then one (sometimes two) additional virtual function calls that ultimately returns a vtable pointer. It is this pointer that is the sole member inside the MouseCapabilities variable. A nullptr_t is thus a natural way to avoid all of this construction.

        Regarding properties it again comes down to the fact that every call, whether it appears to be a property or a method, must make it across the ABI to the component and this again is achieved via virtual function calls. A data member would not make that happen very efficiently.

      3. Joren

        I see, and I appreciate your clarification. Although, in the context of “Standard C++”, which you mention so often, this is still construction. No matter how simple or complex the stuff that goes on behind the scenes, an object is constructed.

        Don’t get me wrong, I like your post, but I was put off a little by statements on “Standard C++” in the context of the Windows API, making it non-standard C++.

      4. Kenny Kerr Post author

        That’s fair. Forgive my poor attempts to explain. I am simply proposing that a Standard C++ compiler can be used for platform-specific app development without having to invent a new language (C#) or bastardize an existing one (C++/CX).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s