{"id":109537,"date":"2024-03-15T07:00:00","date_gmt":"2024-03-15T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109537"},"modified":"2024-03-18T14:32:10","modified_gmt":"2024-03-18T21:32:10","slug":"20240315-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240315-00\/?p=109537","title":{"rendered":"How well does WRL <CODE>ComPtr<\/CODE> support class template argument deduction (CTAD)?"},"content":{"rendered":"<p>Continuing our investigation of <a title=\"Class template argument deduction (CTAD) and C++ COM wrappers, part 1: Initial explorations\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240311-00\/?p=109521\"> which C++ COM wrappers support class template argument deduction<\/a> (CTAD), next up is WRL&#8217;s <code>ComPtr<\/code>.<\/p>\n<p>WRL fails CTAD because it tries too hard. The from-raw-pointer constructor is this one:<\/p>\n<pre>template &lt;typename T&gt;\r\nclass ComPtr\r\n{\r\npublic:\r\n    typedef T InterfaceType;\r\n\r\nprotected:\r\n    InterfaceType *ptr_;\r\n\r\npublic:\r\n    template&lt;class U&gt;\r\n    ComPtr(_In_opt_ U *other) throw() : ptr_(other) \/\/ this one\r\n    {\r\n        InternalAddRef();\r\n    }\r\n\r\n    \u27e6 other stuff \u27e7\r\n};\r\n<\/pre>\n<p>WRL thinks is being helpful here in that it lets you, for example, initialize a <code>ComPtr&lt;IBase&gt;<\/code> not only with a pointer to <code>IBase<\/code> but also a pointer to anything that derives from <code>IBase<\/code>. If you pass a pointer to a derived class, it upcasts it into a pointer to the base class.<\/p>\n<p>This attempt at being helpful prevents CTAD from working, because the compiler can&#8217;t figure out which <code>T<\/code> to use when it&#8217;s given just a <code>U<\/code>.<\/p>\n<p>WRL could just have written the simple, straightforward constructor:<\/p>\n<pre>    ComPtr(_In_opt_ T *other) throw() : ptr_(other)\r\n    {\r\n        InternalAddRef();\r\n    }\r\n<\/pre>\n<p>If the caller passes a pointer to something that doesn&#8217;t derive from <code>T<\/code>, then the compiler tells you that it can&#8217;t find a matching constructor.<\/p>\n<p>This fancy template constructor is a case of what the Germans call <i lang=\"de\">Verschlimmbesserung<\/i>, which means &#8220;making something worse in a well-intentioned but failed attempt to make it better.&#8221;<\/p>\n<p>But I think I know why the authors of WRL wrote their raw-pointer constructor this way, and it&#8217;s a common problem to library authors: Error message metaprogramming.<\/p>\n<p>If somebody tries to construct a <code>ComPtr&lt;T&gt;<\/code> from a pointer to something unrelated to <code>T<\/code>, the fancy templated version gives the error message<\/p>\n<pre style=\"white-space: pre-wrap;\">error C2440: 'initializing': cannot convert from 'U *' to 'IBase *'\r\nwith\r\n    [\r\n        U=INotDerived\r\n    ]\r\n<\/pre>\n<p>Without the fancy template, the error message would have been<\/p>\n<pre style=\"white-space: pre-wrap;\">error C2440: '&lt;function-style-cast&gt;': cannot convert from 'INotDerived *' to 'Microsoft::<wbr \/>WRL::<wbr \/>ComPtr&lt;IBase&gt;'\r\n'Microsoft::<wbr \/>WRL::<wbr \/>ComPtr&lt;IBase&gt;::<wbr \/>ComPtr' no overloaded function could convert all the argument types\r\ncould be 'ComPtr&lt;IBase&gt;::<wbr \/>ComPtr(<wbr \/>const ComPtr&lt;U&gt; &amp;amp,<wbr \/>typename Details::<wbr \/>EnableIf&lt;<wbr \/>Details::<wbr \/>IsConvertible&lt;<wbr \/>U*, T*&gt;::<wbr \/>value, void *&gt;::<wbr \/>type *)'\r\nwith\r\n    [\r\n        T=IBase\r\n    ]\r\nor 'ComPtr&lt;IBase&gt;::<wbr \/>ComPtr(nullptr)'\r\n'ComPtr&lt;IBase&gt;::ComPtr(nullptr)': cannot convert argument 1 from 'INotDerived *' to 'nullptr'\r\nor\r\n    \u27e6 repeat for the other constructors \u27e7\r\n<\/pre>\n<p>A simple error on the client turns into an explosive and incomprehensible error message.<\/p>\n<p>The library author can get the best of both worlds by providing both the fancy templated constructor (to improve the error message) and a simple, straightforward constructor (for CTAD):<\/p>\n<pre>template &lt;typename T&gt;\r\nclass ComPtr\r\n{\r\npublic:\r\n    typedef T InterfaceType;\r\n\r\nprotected:\r\n    InterfaceType *ptr_;\r\n\r\npublic:\r\n    \/\/ This one for CTAD\r\n    ComPtr(_In_opt_ T *other) throw() : ptr_(other)\r\n    {\r\n        InternalAddRef();\r\n    }\r\n\r\n    \/\/ This one to improve error message\r\n    template&lt;class U&gt;\r\n    ComPtr(_In_opt_ U *other) throw() :\r\n        \/\/ <span style=\"text-decoration: line-through;\">ComPtr(static_cast&lt;T*&gt;(other)) {}<\/span>\r\n        ComPtr(MustDeriveFromT(other)) {}\r\n\r\n    \u27e6 other stuff \u27e7\r\n\r\nprivate:\r\n    constexpr static T* MustDeriveFromT(T* p) { return p; }\r\n};\r\n<\/pre>\n<p>Or the library author can provide a deduction guide to steer CTAD to the correct type:<\/p>\n<pre>template&lt;typename T&gt; ComPtr(T*) -&gt; ComPtr&lt;T&gt;;\r\n<\/pre>\n<p>Since WRL was written for C++11, you&#8217;d have to put the deduction guide inside an <code>#ifdef<\/code> to hide it from pre-C++17 compilers.<\/p>\n<p>As a consumer, though, you shouldn&#8217;t be creating deduction guides for somebody else&#8217;s classes. Instead, you can use a maker function.<\/p>\n<pre>template&lt;typename T&gt;\r\nComPtr&lt;T&gt; MakeComPtr(T* p)\r\n{\r\n    return p;\r\n}\r\n<\/pre>\n<p>Next time, we&#8217;ll look at wil, which has a different category of problems.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It tries too hard and accidentally breaks CTAD.<\/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-109537","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>It tries too hard and accidentally breaks CTAD.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109537","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=109537"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109537\/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=109537"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109537"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109537"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}