{"id":107946,"date":"2023-03-17T07:00:00","date_gmt":"2023-03-17T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=107946"},"modified":"2023-03-17T07:10:31","modified_gmt":"2023-03-17T14:10:31","slug":"20230317-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230317-00\/?p=107946","title":{"rendered":"Exploiting C++\/WinRT CRTP: Property and event declarations"},"content":{"rendered":"<p>In C++\/WinRT, properties are represented by method calls. Given this Windows Runtime class definition:<\/p>\n<pre>namespace MyNamespace\r\n{\r\n    runtimeclass Widget\r\n    {\r\n        Double Height;\r\n    }\r\n}\r\n<\/pre>\n<p>you can access the property like so:<\/p>\n<pre>Widget w;\r\n\r\n\/\/ 0 parameters = get value\r\nauto height = w.Height();\r\n\r\n\/\/ 1 parameter = set value\r\nw.Height(newHeight);\r\n<\/pre>\n<p>If you implement this class in C++\/WinRT, the auto-generated header files use the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Curiously_recurring_template_pattern\"> Curiously Recurring Template Pattern<\/a> (commonly known as CRTP) and expect your implementation class to follow the same pattern:<\/p>\n<pre>namespace winrt::MyNamespace::implementation\r\n{\r\n    struct Widget : WidgetT&lt;Widget&gt;\r\n    {\r\n        double m_height;\r\n        auto Height() const { return m_height; }\r\n        void Height(double const&amp; height) { m_height = height; }\r\n    };\r\n}\r\n<\/pre>\n<p>If you have a lot of properties, writing these accessor methods can be quite tedious and therefore error-prone.<\/p>\n<p>But we can exploit CRTP to make it easier.<\/p>\n<p>You see, the CRTP wrappers don&#8217;t actually require <code>Height()<\/code> to be a method. It just requires that <code>Height()<\/code> be an expression that represents the property value. Similarly, <code>Height(newValue)<\/code> just needs to be an expression whose side effect is changing the property value.<\/p>\n<pre>\/\/ This code is autogenerated by C++\/WinRT\r\n\r\nint32_t get_Height(double* value) noexcept final try\r\n{\r\n    typename D::<a title=\"C++\/WinRT implementation extension points: abi_guard, abi_enter, abi_exit, and final_release\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20191018-00\/?p=103010\">abi_guard<\/a> guard(this-&gt;shim());\r\n    *value = detach_from&lt;double&gt;(this-&gt;shim().Height());\r\n    return 0;\r\n}\r\ncatch (...) { return to_hresult(); }\r\n\r\nint32_t put_Height(double value) noexcept final try\r\n{\r\n    typename D::abi_guard guard(this-&gt;shim());\r\n    this-&gt;shim().Height(value);\r\n    return 0;\r\n}\r\ncatch (...) { return to_hresult(); }\r\n<\/pre>\n<p>In words, the autogenerated code expects <code>d.Height()<\/code> to produce something that can be stored in a <code>double<\/code>, and it expects <code>d.Height(value)<\/code> to do something with the <code>value<\/code>.<\/p>\n<p>These look like method calls, but they don&#8217;t have to be. They can be anything, provided that sequence of tokens produces a valid result.<\/p>\n<p>Specifically, they can be callable objects.<\/p>\n<pre>template&lt;typename T&gt;\r\nstruct winrt_property\r\n{\r\n    winrt_property(T const&amp; initial =\r\n        <a title=\"Producing an empty Windows Runtime type in C++\/WinRT\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220504-00\/?p=106569\">winrt_empty_value<\/a>&lt;T&gt;()) :\r\n        value(initial) {}\r\n\r\n    T operator()() const { return value; }\r\n    void operator()(T const&amp; newValue) { value = newValue; }\r\n\r\n    T value;\r\n};\r\n\r\nnamespace winrt::MyNamespace::implementation\r\n{\r\n    struct Widget : WidgetT&lt;Widget&gt;\r\n    {\r\n        winrt_property&lt;double&gt; Height;\r\n    };\r\n}\r\n<\/pre>\n<p>When the C++\/WinRT library does <code>this-&gt;shim().Height()<\/code>, this obtains the <code>Widget<\/code> implementation class and then looks up <code>Height<\/code>, which is a member variable, and then applies the <code>()<\/code> function call operator, which we overloaded to return the current wrapped value.<\/p>\n<p>Similarly, when the C++\/WinRT library does <code>this-&gt;shim().Height(value)<\/code>, this invokes the overloaded single-parameter function call operator which updates the value.<\/p>\n<p>In the implementation, you can access the value either by using the function call operator:<\/p>\n<pre>void Widget::IncreaseHeight(double increment)\r\n{\r\n    auto currentHeight = Height();\r\n    Height(currentHeight + increment);\r\n}\r\n<\/pre>\n<p>or you can access the <code>value<\/code> member, which was intentionally left public for this purpose.<\/p>\n<pre>void Widget::IncreaseHeight(double increment)\r\n{\r\n    Height.value += increment;\r\n}\r\n<\/pre>\n<p>You can use the same trick for C++\/WinRT event implementations.<\/p>\n<pre>template&lt;typename D&gt;\r\nstruct winrt_event : winrt::event&lt;D&gt;\r\n{\r\n    winrt_event() = default;\r\n\r\n    using winrt::event&lt;D&gt;::operator();\r\n    auto operator()(D const&amp; handler)\r\n        { return this-&gt;add(handler); }\r\n    void operator()(winrt::event_token const&amp; token)\r\n        { this-&gt;remove(token); }\r\n};\r\n\r\n\/\/ Class definition\r\n\r\nnamespace MyNamespace\r\n{\r\n    runtimeclass Widget\r\n    {\r\n        Windows.Foundation.TypedEventHandler&lt;Widget, Object&gt; Closed;\r\n    }\r\n}\r\n\r\n\/\/ Implementation\r\nnamespace winrt::MyNamespace::implementation\r\n{\r\n    \/\/ Just an abbreviation to save some typing.\r\n    using ClosedHandler = Windows::Foundation::TypedEventHandler&lt;\r\n        MyNamespace::Widget, Windows::Foundation::IInspectable&gt;;\r\n\r\n    struct Widget : WidgetT&lt;Widget&gt;\r\n    {\r\n        winrt_event&lt;ClosedHandler&gt; Closed;\r\n    };\r\n}\r\n<\/pre>\n<p>Note <a title=\"Mind your C++\/WinRT namespaces\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230316-00\/?p=107944\"> the need to namespace-qualify <code>Widget<\/code><\/a> when it appears as the first template type parameter of <code>Typed\u00adEvent\u00adHandler<\/code> because we want it to be the projected type and not the implementation type.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>They don&#8217;t have to be methods; they just have to look like methods.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-107946","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>They don&#8217;t have to be methods; they just have to look like methods.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107946","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=107946"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107946\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=107946"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=107946"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=107946"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}