{"id":106362,"date":"2022-03-18T07:00:00","date_gmt":"2022-03-18T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=106362"},"modified":"2023-11-12T17:32:27","modified_gmt":"2023-11-13T01:32:27","slug":"20220318-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220318-00\/?p=106362","title":{"rendered":"Making our multiple-interface query more C++-like, part 2"},"content":{"rendered":"<p>Last time, <a title=\"Making our multiple-interface query more C++-like, part 1\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220317-00\/?p=106359\"> we wrote a C++-style wrapper around <code>Co\u00adCreat\u00adInstance\u00adEx<\/code><\/a> that treats all of the interfaces as optional. But probably, you are issuing the multiple-interface query in the case where some or perhaps even all of the interfaces are required.<\/p>\n<pre>auto [widget, objectWithSite, persistFile] =\r\n    CreateInstanceMultiQI&lt;IWidget, IObjectWithSite, IPersistFile&gt;\r\n        (CLSID_Widget, nullptr, CLSCTX_LOCAL_SERVER);\r\n<\/pre>\n<p>The <code>IWidget<\/code> is required. Without that, you have nothing. But maybe the <code>IObjectWithSite<\/code> is optional: If the widget doesn&#8217;t support <code>IObjectWithSite<\/code>, we just won&#8217;t set a site on it.<\/p>\n<p>We need a way to tell our <code>Create\u00adInstance\u00adMulti\u00adQI<\/code> function that some of the interfaces are required and others are optional.<\/p>\n<p>I&#8217;m going to pull a dirty trick. and instead of creating my own marker, I&#8217;m going to reuse an existing template from the standard library, since it has such a great name: <code>std::optional<\/code>. I&#8217;m going to say that all of the interfaces are required by default, but you can wrap them inside a <code>std::optional<\/code> to say that they&#8217;re optional.<\/p>\n<pre>auto [widget, objectWithSite, persistFile] =\r\n    CreateInstanceMultiQI&lt;\r\n        IWidget,\r\n        <span style=\"border: solid 1px currentcolor;\">std::optional&lt;IObjectWithSite&gt;<\/span>,\r\n        IPersistFile&gt;\r\n        (CLSID_Widget, nullptr, CLSCTX_LOCAL_SERVER);\r\n<\/pre>\n<p>Most of the stuff we wrote last time still works. We just need to add a validation step that verifies that all of the required interfaces were successfully obtained.<\/p>\n<pre>template&lt;typename Interface&gt;\r\nstruct multiqi_traits\r\n{\r\n    using type = Interface;\r\n    static constexpr bool is_required = true;\r\n};\r\n\r\ntemplate&lt;typename Interface&gt;\r\nstruct multiqi_traits&lt;std::optional&lt;Interface&gt;&gt;\r\n{\r\n    using type = Interface;\r\n    static constexpr bool is_required = false;\r\n};\r\n\r\ntemplate&lt;typename Interface&gt;\r\nusing multiqi_traits_com_ptr =\r\n    wil::com_ptr&lt;typename multiqi_trauts&lt;Interface&gt;::type&gt;;\r\n<\/pre>\n<p>The <code>multiqi_<wbr \/>traits<\/code> template traits type assumes that every interface is required. The specialization unwraps any interface that is wrapped inside a <code>std::optional<\/code> and remembers that it is not required. For convenience, we also define a <code>multiqi_<wbr \/>traits_<wbr \/>com_<wbr \/>ptr<\/code> that represents the final <code>com_ptr<\/code> we want to return.<\/p>\n<p>We can use this traits type to modify our existing function to throw if any required interface was not obtained:<\/p>\n<pre>template&lt;typename... Interfaces, std::size_t... Ints&gt;\r\nauto CreateInstanceMultiQIWorker(\r\n    REFCLSID clsid, IUnknown* punkOuter,\r\n    DWORD clsctx, MULTI_QI* mqi,\r\n    std::index_sequence&lt;Ints...&gt;)\r\n{\r\n    THROW_IF_FAILED(\r\n        CoCreateInstanceEx(clsid, punkOuter, clsctx,\r\n                           sizeof...(Interfaces), mqi));\r\n\r\n    std::tuple&lt;<span style=\"border: solid 1px currentcolor;\">multiqi_traits_com_ptr&lt;Interfaces&gt;<\/span>...&gt; t;\r\n    ((std::get&lt;Ints&gt;(t).attach(\r\n        static_cast&lt;<span style=\"border: solid 1px currentcolor;\">typename multiqi_traits&lt;Interfaces&gt;::type<\/span>*&gt;\r\n        (mqi[Ints].pItf))), ...);\r\n\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">([&amp;] {                                                      <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    if constexpr (multiqi_traits&lt;Interfaces&gt;::is_required) {<\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        THROW_IF_FAILED(mqi[Ints].hr);                      <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    }                                                       <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">}(), ...);                                                  <\/span>\r\n\r\n    return t;\r\n}\r\n\r\ntemplate&lt;typename... Interfaces&gt;\r\nstd::tuple&lt;<span style=\"border: solid 1px currentcolor;\">multiqi_traits_com_ptr&lt;Interfaces&gt;<\/span>...&gt;\r\nCreateInstanceMultiQI(\r\n    REFCLSID clsid, IUnknown* punkOuter,\r\n    DWORD clsctx)\r\n{\r\n    MULTI_QI mqi[] = {\r\n        MULTI_QI{ &amp;__uuidof(<span style=\"border: solid 1px currentcolor;\">typename multiqi_traits&lt;Interfaces&gt;::type<\/span>),\r\n          nullptr, 0 }...\r\n    };\r\n\r\n    return CreateInstanceMultiQIWorker&lt;Interfaces...&gt;\r\n        (clsid, punkOuter, clsctx, mqi,\r\n         std::index_sequence_for&lt;Interfaces...&gt;{});\r\n}\r\n<\/pre>\n<p>The first change is mechanical: We have to use <code>multiqi_<wbr \/>traits<\/code> to unwrap the elements of <code>Interfaces...<\/code>, because some of them may be <code>std::optional&lt;T&gt;<\/code>, and in those cases, we want to reach inside the <code>std::optional<\/code> and extract the <code>T<\/code>.<\/p>\n<p>The new part is the template parameter pack expansion of a lambda invocation. Template parameter pack expansions can expand expressions, but we need to expand an <code>if<\/code> statement. No problem: We wrap the <code>if<\/code> statement in a lambda, and then evaluate the lambda immediately, and then take the lambda evaluation (now an expression!) and make the evaluation the thing that is given to the template parameter pack expansion.<\/p>\n<p>Note that we perform the validation as a separate step after transferring the raw interface pointers into the tuple. This attempted optimization would be incorrect:<\/p>\n<pre>    ([&amp;] {\r\n        std::get&lt;Ints&gt;(t).attach(\r\n        static_cast&lt;typename multiqi_traits&lt;Interfaces&gt;::type*&gt;\r\n        (mqi[Ints].pItf));\r\n\r\n        if constexpr (multiqi_traits&lt;Interfaces&gt;::is_required) {\r\n            THROW_IF_FAILED(mqi[Ints].hr);\r\n        }\r\n    }(), ...);\r\n<\/pre>\n<p>If any of the required interfaces were not found, then the above incorrect version throws an exception immediately, leaking all of the interface pointers that hadn&#8217;t yet been processed. We need to move the raw pointers into smart pointers first, so they don&#8217;t leak, and only then can we start throwing exceptions.<\/p>\n<p><b>Bonus chatter<\/b>: Note that this code allows you to call <code>Create\u00adInstance\u00adMulti\u00adQI<\/code> and specify that all of the interfaces are optional. That&#8217;s intentional, because you might have this:<\/p>\n<pre>auto [widget, doodad] =\r\n    CoCreateInstanceMultiQI&lt;\r\n        std::optional&lt;IWidget&gt;,\r\n        std::optional&lt;IDoodad&gt;\r\n    &gt;(clsid, nullptr, CLSCTX_ANY);\r\n<\/pre>\n<p>In this case, both interfaces are tagged as optional. You don&#8217;t care whether the object is a widget or doodad, but it needs to be one of the two. (Because <code>Co\u00adCreate\u00adInstance\u00adEx<\/code> will fail if <i>none<\/i> of the interfaces is supported.)<\/p>\n<p>If you want to create the object, with the possibility that it is <i>neither<\/i> a widget nor a doodad, you can specify <code>IUnknown<\/code> as a required interface. Every COM object supports <code>IUnknown<\/code>, so you know that will succeed if the object can be created at all.<\/p>\n<p><b>Bonus bonus chatter<\/b>: I put the required checks inside the <code>Create\u00adInstance\u00adMulti\u00adQI\u00adWorker<\/code>, which means that you get a separate version of <code>Create\u00adInstance\u00adMulti\u00adQI\u00adWorker<\/code> for each arity, as well for each pattern of required\/optional. To improve code sharing, we could factor out the part of the worker that is independent of the required\/optional pattern, so that function could be shared among all uses with the same number of interfaces.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Letting you specify which interfaces are required and which are optional.<\/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-106362","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Letting you specify which interfaces are required and which are optional.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106362","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=106362"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106362\/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=106362"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=106362"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=106362"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}