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"http://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"http://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"http://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.