{"id":111787,"date":"2025-11-13T07:00:00","date_gmt":"2025-11-13T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=111787"},"modified":"2025-12-28T15:04:27","modified_gmt":"2025-12-28T23:04:27","slug":"20251113-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20251113-00\/?p=111787","title":{"rendered":"Could we use CTAD to simplify the use of WRL&#8217;s Callback function?"},"content":{"rendered":"<p>Commenter Bwmat asked, &#8220;<a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250919-00\/?p=111612&amp;commentid=143196#comment-143196\">Can CTAD avoid the need for the lengthy type name in the last example<\/a>?&#8221; This was in response to the rather lengthy type in the explicit specialization<\/p>\n<pre>void MyClass::RegisterCompletion(ABI::IAsyncAction* action)\r\n{\r\n    m_showingToken = inputPane-&gt;put_Showing(\r\n        <span style=\"border-bottom: 1px currentcolor solid;\">Microsoft::WRL::Callback<\/span><span style=\"border: solid 1px currentcolor; border-bottom: none;\">&lt;ABI::ITypedEventHandler&lt;<\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    ABI::InputPane*,                             <\/span>\r\n        <span style=\"border: solid 1px currentcolor; border-top: none;\">    ABI::InputPaneVisibilityEventArgs*&gt;&gt;<\/span><span style=\"border-top: 1px currentcolor solid;\">(        <\/span>\r\n            this, &amp;MyClass::OnInputPaneShowing).Get());\r\n}\r\n<\/pre>\n<p>Now, you cannot literally use CTAD (Class Template Argument Deduction) here because <code>Callback<\/code> is not a class.<\/p>\n<p>Class template argument deduction exists to cover for the lack of type deduction in class naming. But functions <i>do<\/i> have type deduction! So let&#8217;s use it.<\/p>\n<p>We&#8217;re starting with this:<\/p>\n<pre>template&lt;\r\n   typename TDelegateInterface,\r\n   typename TCallbackObject,\r\n   typename... TArgs\r\n&gt;\r\nComPtr&lt;TDelegateInterface&gt; Callback(\r\n   _In_ TCallbackObject *object,\r\n   _In_ HRESULT (TCallbackObject::* method)(TArgs...)\r\n);\r\n<\/pre>\n<p>We want to deduce the <code>TDelegateInterface<\/code> to be <code>TypedEventHandler&lt;TArgs...&gt;<\/code>:<\/p>\n<pre>template&lt;\r\n   typename TDelegateInterface\r\n        <span style=\"border: solid 1px currentcolor;\">= TypedEventHandler&lt;TArgs...&gt;,<\/span>\r\n   typename TCallbackObject,\r\n   typename... TArgs\r\n&gt;\r\nComPtr&lt;TDelegateInterface&gt; Callback(\r\n   _In_ TCallbackObject *object,\r\n   _In_ HRESULT (TCallbackObject::* method)(TArgs...)\r\n);\r\n<\/pre>\n<p>Unfortunately, this doesn&#8217;t work because template default parameters cannot refer to future template parameters.<\/p>\n<p>We could try reordering the parameters.<\/p>\n<pre>template&lt;\r\n   <span style=\"border: solid 1px currentcolor; border-bottom: none;\">typename TCallbackObject,         <\/span>\r\n   <span style=\"border: 1px currentcolor; border-style: none solid;\">typename... TArgs,                <\/span>\r\n   <span style=\"border: 1px currentcolor; border-style: none solid;\">typename TDelegateInterface       <\/span>\r\n   <span style=\"border: solid 1px currentcolor; border-top: none;\">     = TypedEventHandler&lt;TArgs...&gt;<\/span>\r\n&gt;\r\nComPtr&lt;TDelegateInterface&gt; Callback(\r\n   _In_ TCallbackObject *object,\r\n   _In_ HRESULT (TCallbackObject::* method)(TArgs...)\r\n);\r\n<\/pre>\n<p>However, this doesn&#8217;t work because template parameter packs must come at the end of the template parameter list.<\/p>\n<p>We can finesse the problem by splitting the template into one that specifically has exactly two <code>TArgs<\/code> and one for the other cases.<\/p>\n<pre>template&lt;\r\n   typename TDelegateInterface,\r\n   typename TCallbackObject,\r\n   typename... TArgs,\r\n&gt;\r\nComPtr&lt;TDelegateInterface&gt; Callback(\r\n   _In_ TCallbackObject *object,\r\n   _In_ HRESULT (TCallbackObject::* method)(TArgs...)\r\n);\r\n\r\ntemplate&lt;\r\n    typename TCallbackObject,\r\n    typename TArg1, typename TArg2,\r\n    typename TDelegateInterface =\r\n        TypedEventHandler&lt;TArg1, TArg2&gt;\r\n&gt;\r\nComPtr&lt;TDelegateInterface&gt; Callback(\r\n   _In_ TCallbackObject *object,\r\n   _In_ HRESULT (TCallbackObject::* method)(TArgs1, TArg2)\r\n);\r\n<\/pre>\n<p>Unfortunately, this fails due to the ambiguous call. We have to remove the first one from consideration when the number of parameters to the callback is exactly two.<\/p>\n<pre>template&lt;\r\n   typename TDelegateInterface,\r\n   typename TCallbackObject,\r\n   typename... TArgs,\r\n&gt;\r\nstd::enable_if_t&lt;sizeof...(TArgs) != 2,\r\n    ComPtr&lt;TDelegateInterface&gt;&gt;\r\nCallback(\r\n   _In_ TCallbackObject *object,\r\n   _In_ HRESULT (TCallbackObject::* method)(TArgs...)\r\n);\r\n\r\ntemplate&lt;\r\n    typename TCallbackObject,\r\n    typename TArg1, typename TArg2,\r\n    typename TDelegateInterface =\r\n        TypedEventHandler&lt;TArg1, TArg2&gt;\r\n&gt;\r\nComPtr&lt;TDelegateInterface&gt; Callback(\r\n   _In_ TCallbackObject *object,\r\n   _In_ HRESULT (TCallbackObject::* method)(TArg1, TArg2)\r\n);\r\n<\/pre>\n<p>But even with this version, the two-argument callback has poor ergonomics: If you want to specify a custom delegate interface (like, say, <code>Suspending\u00adEvent\u00adHandler<\/code>), you have to slog through the first three parameters so you can finally specify the last one.<\/p>\n<pre>Callback&lt;<span style=\"border: solid 1px currentcolor;\">MyObject, IInspectable*, SuspendingEventArgs*,<\/span> SuspendingEventHandler&gt;(\r\n    this, &amp;MyObject::OnSuspending);\r\n<\/pre>\n<p>Instead, we can use <a title=\"Reordering C++ template type parameters for usability purposes, and type deduction from the future\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230609-00\/?p=108318\"> type deduction from the future<\/a>.<\/p>\n<pre>template&lt;\r\n   typename TDelegateInterface <span style=\"border: solid 1px currentcolor;\">= void<\/span>,\r\n   typename TCallbackObject,\r\n   typename... TArgs\r\n&gt;\r\nComPtr&lt;std::conditional_t&lt;\r\n    std::is_same_v&lt;TDelegateInterface, void&gt;,\r\n    TypedEventHandler&lt;TArgs...&gt;,\r\n    TDelegateInterface&gt;&gt;\r\nCallback(\r\n   _In_ TCallbackObject *object,\r\n   _In_ HRESULT (TCallbackObject::* method)(TArgs...)\r\n);\r\n<\/pre>\n<p>But wait, the <code>Typed\u00adEvent\u00adHandler<\/code> takes only two template arguments, so if we invoke <code>Callback<\/code> with a pointer to member function that takes only one argument, we get a compiler error because it cannot form <code>TypedEventHandler&lt;TArg&gt;<\/code>.<\/p>\n<pre>struct MyObject\r\n{\r\n    HRESULT OnUIInvoked(IUICommand* command);\r\n};\r\n\r\nCallback&lt;UICommandInvokedHandler&lt;(this, &amp;MyObject::OnUIInvoked);\r\n\/\/ ^^^ error: template argument deduction\/substitution failed\r\n\/\/            wrong number of template arguments to TypedEventHandler\r\n<\/pre>\n<p>We have to delay the mention of <code>TypedEventHandler&lt;TArgs...&gt;<\/code> until we are committed to using it.<\/p>\n<pre><span style=\"border: solid 1px currentcolor; border-bottom: none;\">template&lt;typename... TArgs&gt;                  <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">struct TypedEventHandlerHolder               <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">{                                            <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">    using type = TypedEventHandler&lt;TArgs...&gt;;<\/span>\r\n<span style=\"border: solid 1px currentcolor; border-top: none;\">};                                           <\/span>\r\n\r\ntemplate&lt;\r\n   typename TDelegateInterface = void,\r\n   typename TCallbackObject,\r\n   typename... TArgs\r\n&gt;\r\nComPtr&lt;<span style=\"border: solid 1px currentcolor;\">typename<\/span> std::conditional_t&lt;\r\n    std::is_same_v&lt;TDelegateInterface, void&gt;,\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">TypedEventHandlerHolder&lt;TArgs...&gt;,     <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">std::type_identity&lt;TDelegateInterface&gt;&gt;<\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">    ::type                             <\/span>\r\n&gt;\r\nCallback(\r\n   _In_ TCallbackObject *object,\r\n   _In_ HRESULT (TCallbackObject::* method)(TArgs...)\r\n);\r\n<\/pre>\n<p>Now, back at the top, I mentioned that CTAD doesn&#8217;t apply because CTAD is for class templates, but <code>Callback<\/code> is a function template.<\/p>\n<p>But what if we made <code>Callback<\/code> a class template?<\/p>\n<pre>template&lt;\r\n    typename TDelegateInterface,\r\n    typename TCallbackObject,\r\n    typename... TArgs\r\n&gt;\r\nstruct Callback : ComPtr&lt;TDelegateInterface&gt;\r\n{\r\n    Callback(TCallbackObject *object,\r\n             HRESULT (TCallbackObject::* method)(TArgs...));\r\n};\r\n<\/pre>\n<p>Now, CTAD will infer the <code>TCallbackObject<\/code> and <code>TArgs<\/code>, and we can use a deduction guide to infer the <code>TDelegateInterface<\/code>.<\/p>\n<pre>template&lt;\r\n    typename TCallbackObject,\r\n    typename TArg1, TArg2\r\n&gt;\r\nCallback(TCallbackObject*,\r\n         HRESULT (TCallbackObject::*)(TArg1, TArg2))\r\n    -&gt; Callback&lt;TypedEventHandler&lt;TArg1, TArg2&gt;, TCallbackObject, TArg1, TArg2&gt;;\r\n<\/pre>\n<p>Unfortunately, CTAD doesn&#8217;t work with partial specialization, so you can&#8217;t write<\/p>\n<pre>Callback&lt;SuspendingEventHandler&gt;(this, &amp;MyObject::OnSuspending);\r\n<\/pre>\n<p>So I guess we&#8217;re stuck with the function overload with type deduction from the future.<\/p>\n<p>But really, I think <a href=\"https:\/\/en.wikipedia.org\/wiki\/WarGames\"> the winning move is not to play<\/a>.<\/p>\n<p>Instead of trying to make <code>Callback<\/code> fancy, create a separate function <code>TypedEventHandlerCallback<\/code>.<\/p>\n<pre>template&lt;\r\n   typename TCallbackObject,\r\n   typename TArg1, typename TArg2&gt;\r\nComPtr&lt;TypedEventHandler&lt;TArg1, TArg2&gt;&gt;\r\nTypedEventHandlerCallback(\r\n    TCallbackObject* object,\r\n    HRESULT (TCallbackObject::*method)(TArg1, TArg2));\r\n<\/pre>\n<p>Then the original code would be<\/p>\n<pre>void MyClass::RegisterCompletion(ABI::IAsyncAction* action)\r\n{\r\n    m_showingToken = inputPane-&gt;put_Showing(\r\n        TypedEventCallback(this, &amp;MyClass::OnInputPaneShowing).Get());\r\n}\r\n<\/pre>\n<p>While we&#8217;re there, we can also make an <code>EventHandlerCallback<\/code> function.<\/p>\n<pre>template&lt;\r\n   typename TCallbackObject,\r\n   typename TArg&gt;\r\nComPtr&lt;EventHandler&lt;TArg&gt;&gt;\r\nEventHandlerCallback(\r\n    TCallbackObject* object,\r\n    HRESULT (TCallbackObject::*method)(IInspectable*, TArg));\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Not directly, but maybe indirectly.<\/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-111787","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Not directly, but maybe indirectly.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111787","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=111787"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111787\/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=111787"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=111787"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=111787"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}