C++/WinRT gotcha: Setting properties incorrectly

Raymond

Getting and setting a Windows Runtime property looks like this:

Language Getter Setter
C# v = o.Property; o.Property = 42;
C++/CX v = o->Property; o->Property = 42;
C++/WinRT v = o.Property(); o.Property(42);
JavaScript v = o.property; o.property = 42;
Python v = o.property o.property = 42

Somebody is the odd man out.

All the projections use a simple member access to read a property and a simple assignment statement to set a property, with the exception of C++/WinRT, which uses a function call in both places.

That’s because the standard C++ language doesn’t have “properties”, and C++/WinRT is a projection of the Windows Runtime into standard C++. (C++/CX gets away with it because it’s not standard C++.)

If you’re translating existing code from one of the other languages to C++/WinRT, you may realize that properties need to change to function calls, but in your haste (or tiredness), you mistakenly convert o.Property = 42 to

    o.Property() = 42;

Fortunately, this gives you a compiler error because you cannot assign to an integer value.

Unfortunately, if the property has a non-primitive type, you don’t get an error.

    o.Name() = L"Fred";
    // oops: Should be o.Name(L"Fred");

    lv.Background() = greenBrush;
    // oops: Should be lv.Background(greenBrush);

That’s because you are assigning to the temporary object returned by the property getter method, and that temporary object has an assignment operator.

The above code breaks down like this:

    auto name = o.Name();
    name = L"Fred";
    // destruct temporary "name"

    auto background = lv.Background();
    background = greenBrush;
    // destruct temporary "background"

Congratulations, you updated a temporary that was immediately destructed. Total waste of time.

21 comments

Comments are closed. Login to edit/delete your existing comments

  • Alex Martin

    At least it doesn’t mysteriously corrupt something important?

    • Wil Wilder Apaza Bustamante

      Don’t worry, the C++ standard has a solution to this problem:
      If the class declares its assignment operator with a ref-qualifier:
      `Class& operator=(const Class&) &;`
      the compiler will report an error when trying to assign to a temporary.

      This nicely illustrates the fundamental character of C++: sane behavior is possible if you’re careful; but insanity is the default.

      • Raymond ChenMicrosoft employee

        The C++ standard giveth, and the C++ standard taketh away. If a base class has a Base& operator=(const Base&)& method (denying the rvalue assignment), the derived class's default copy assignment operator does not have the lvalue qualifier, so it can be used on rvalues again!

        • Ben Voigt

          The C++ language has all the tools for building smart property members as a library (using proxy objects). I did it way back with C++98. It wasn’t worth the trouble, because C++ code using “smart property” members breaks every paradigm and programmer expectation.

          Notably, the C++ way using proxy objects results in properties that can be passed by reference (although they aren’t type-compatible with references to plain data of the same type) which is something that all the newer languages struggle with.

  • Simon Clarkstone

    Do linters pick this mistake up? If the assignment has no effect then that needs a warning, and if the assignment does have an effect then that is a bizarre design that deserves a warning too.

    • Raymond ChenMicrosoft employee

      The assignment has an effect (it bumps the string reference count, and then drops it when the temporary destructs), and since the effect is externally observable, the compiler assumes it was intentional.

      • Simon Clarkstone

        Ah, thanks. I didn’t think of reference-counting as I haven’t touched C++ for about 20 years and used it very little before that. I agree the compiler is doing the right thing there, but from a human point of view are there any cases where assigning to a temporary like this is both a useful and clear way of accomplishing something?

        • Raymond ChenMicrosoft employee

          Even if it were std::string, the compiler doesn’t optimize it out. In the special case of std::string, the compiler would be permitted to do so because std::string is defined by the standard library. But if it were folly::fbstring, the compiler is probably going to perform the copy.

  • Joshua Hudson

    Are you sure you don’t want to see properties in current C++? I did them back in college and I’m sure it still works. Just don’t use them on temporary copies of objects. I always made vectors of classes as vectors of pointers to classes so my property implementation didn’t have to care.

  • Ivan K

    „Äč Congratulations, you updated a temporary that was immediately destructed. Total waste of time.

    Stop whinging. :-p

  • Gunnar Dalsnes

    Any reason for not using the more common (?) o.GetProperty() / o.SetProperty(…) naming?

    • Ray

      What if that would have been done, and the WinRT class also defined an actual “GetProperty” or “SetProperty” method besides a “Property” property? Wouldn’t that clash? despite poor naming efforts in first place.

      • Kalle Niemitalo

        Windows Metadata (WinMD) files notes that property accessor methods have “get_” and “put_” prefixes in their names. I imagine C++/WinRT could have surfaced those names unchanged and there wouldn’t then have been any clashes.

        • Gunnar Dalsnes

          Again, really weird that put was choosen above set, but maybe its just me:-)

          • Ben Voigt

            Thank classic VB which had two assignment statements, Set and Let. That pattern leaked into COM and from there into every subsequent Windows object metamodel. “Put” thus makes it clear that this is not VB Let or VB Set (actually any given usage of put probably overlaps one of the other two, but no promises made as to which one).

  • Neil Rashbrook

    I guess the other approach would be for o.Property() to return an intermediate object, such that if you tried to use it then you would get the property for real, but if you tried to assign to it then it would set the property, in much the same way that array-like objects allow you to write o[1] = o[0];.

    • Raymond ChenMicrosoft employee

      But then auto result = o.Property(); o.destroy(); if (result == L"Hello") { ... } would now use o after it has been destroyed.

    • Raymond ChenMicrosoft employee

      But that also removes the ability to perform non-const operations on the result, like SomeOtherFunction(detach_abi(o.Name())).