{"id":112654,"date":"2026-08-28T07:00:00","date_gmt":"2026-08-28T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112654"},"modified":"2026-08-28T20:09:37","modified_gmt":"2026-08-29T03:09:37","slug":"20260828-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260828-00\/?p=112654","title":{"rendered":"On forcing all derived classes to implement a specific non-virtual method, part 2"},"content":{"rendered":"<p>Last time, we observed that <a title=\"On forcing all derived classes to implement a specific non-virtual method, part 1\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260827-00\/?p=112651\"> one way to force all derived classes to implement a specific non-virtual method is simply not to implement it in the base class<\/a> and sit back and wait for the compile-time fireworks. I noted that we can do better than this, though.<\/p>\n<p>The problem is that the compiler tells you what is wrong, but the details are often buried in the &#8220;supplementary error information&#8221;, and it may not be obvious how to dig it out, or maybe you dig it out but you don&#8217;t understand how to fix it.<\/p>\n<p>You can steer people to the correct error by implementing the method as deleted.<\/p>\n<pre>\/\/ C++\/WRL\r\n\r\nstruct OneWayConverter\r\n{\r\n    \/\/ Derived classes must implement Convert()\r\n    HRESULT STDMETHODCALLTYPE Convert(IInspectable* value,\r\n        ABI::Windows::UI::Xaml::Interop::TypeName targetType,\r\n        IInspectable* parameter, HSTRING language,\r\n        IInspectable** result) <span style=\"border: solid 1px currentcolor;\">= delete<\/span>;\r\n\r\n    \/\/ One-way converters cannot convert back\r\n    HRESULT STDMETHODCALLTYPE ConvertBack(IInspectable* \/*value*\/,\r\n        ABI::Windows::UI::Xaml::Interop::TypeName \/*targetType*\/,\r\n        IInspectable* \/*parameter*\/, HSTRING \/*language*\/,\r\n        IInspectable** result)\r\n    {\r\n        *result = nullptr;\r\n        return E_NOTIMPL;\r\n    }\r\n};\r\n\r\n\/\/ C++\/WinRT\r\n\r\nstruct OneWayConverter\r\n{\r\n    \/\/ Derived classes must implement Convert()\r\n    winrt::Windows::Foundation::IInspectable\r\n        Convert(\r\n            winrt::Windows::Foundation::IInspectable const&amp; \/*value*\/,\r\n            winrt::Windows::UI::Xaml::Interop::TypeName const&amp; \/*targetType*\/,\r\n            winrt::Windows::Foundation::IInspectable const&amp; \/*parameter*\/,\r\n            winrt::hstring const&amp; \/*language*\/) <span style=\"border: solid 1px currentcolor;\">= delete<\/span>;\r\n\r\n    \/\/ One-way converters cannot convert back\r\n    winrt::Windows::Foundation::IInspectable\r\n        ConvertBack(\r\n            winrt::Windows::Foundation::IInspectable const&amp; \/*value*\/,\r\n            winrt::Windows::UI::Xaml::Interop::TypeName const&amp; \/*targetType*\/,\r\n            winrt::Windows::Foundation::IInspectable const&amp; \/*parameter*\/,\r\n            winrt::hstring const&amp; \/*language*\/)\r\n    {\r\n        throw winrt::hresult_not_implemented();\r\n    }\r\n};\r\n\r\n\/\/ Plain C++ analogous scenario\r\n\r\nstruct OneWayConverter\r\n{\r\n    \/\/ Derived classes must implement Convert()\r\n    Color Convert(Widget const&amp;amp \/*value*\/) <span style=\"border: solid 1px currentcolor;\">= delete<\/span>;\r\n\r\n    \/\/ One-way converters cannot convert back\r\n    Widget ConvertBack(Color const&amp; \/*color*\/)\r\n    {\r\n        throw std::exception(\"not implemented\");\r\n    }\r\n};\r\n<\/pre>\n<p>This has a few benefits.<\/p>\n<p>One is that the developer can see the exact function signature that they need to implement: It&#8217;s the one that got deleted in the base class.<\/p>\n<p>Another is that the error message takes them to the deleted function, and if they go to that line of code, they will see the comment that explains why it is deleted.<\/p>\n<pre style=\"white-space: pre-wrap;\">winrt\\windows.ui.xaml.data.h(1469,90): error C2280: 'winrt::<wbr \/>Windows::<wbr \/>Foundation::<wbr \/>IInspectable OneWayConverter::<wbr \/>Convert(<wbr \/>const winrt::<wbr \/>Windows::<wbr \/>Foundation::<wbr \/>IInspectable &amp;,<wbr \/>const winrt::<wbr \/>Windows::<wbr \/>UI::<wbr \/>Xaml::<wbr \/>Interop::<wbr \/>TypeName &amp;,<wbr \/>const winrt::<wbr \/>Windows::<wbr \/>Foundation::<wbr \/>IInspectable &amp;,<wbr \/>const winrt::<wbr \/>hstring &amp;)': attempting to reference a deleted function\r\n      see declaration of 'OneWayConverter::Convert'\r\n      test.cpp(60,9):\r\n      'winrt::<wbr \/>Windows::<wbr \/>Foundation::<wbr \/>IInspectable OneWayConverter::<wbr \/>Convert(<wbr \/>const winrt::<wbr \/>Windows::<wbr \/>Foundation::<wbr \/>IInspectable &amp;,const winrt::<wbr \/>Windows::<wbr \/>UI::<wbr \/>Xaml::<wbr \/>Interop::<wbr \/>TypeName &amp;,const winrt::<wbr \/>Windows::<wbr \/>Foundation::<wbr \/>IInspectable &amp;,<wbr \/>const winrt::<wbr \/>hstring &amp;)': <span style=\"border: solid 1px currentcolor;\">function was explicitly deleted<\/span>\r\n      \u27e6 other error message spew the same as before \u27e7\r\n<\/pre>\n<p>Starting in C++26, you can do even better yet: You can put a custom message directly in the <code>delete<\/code>!<\/p>\n<pre>struct OneWayConverter\r\n{\r\n    \/\/ Derived classes must implement Convert()\r\n    winrt::Windows::Foundation::IInspectable\r\n        Convert(\r\n            winrt::Windows::Foundation::IInspectable const&amp; \/*value*\/,\r\n            winrt::Windows::UI::Xaml::Interop::TypeName const&amp; \/*targetType*\/,\r\n            winrt::Windows::Foundation::IInspectable const&amp; \/*parameter*\/,\r\n            winrt::hstring const&amp; \/*language*\/)\r\n        = delete<span style=\"border: solid 1px currentcolor;\">(\"If you derive from OneWayConverter, you must implement Convert()\")<\/span>;\r\n\r\n    \/\/ One-way converters cannot convert back\r\n    winrt::Windows::Foundation::IInspectable\r\n        ConvertBack(\r\n            winrt::Windows::Foundation::IInspectable const&amp; \/*value*\/,\r\n            winrt::Windows::UI::Xaml::Interop::TypeName const&amp; \/*targetType*\/,\r\n            winrt::Windows::Foundation::IInspectable const&amp; \/*parameter*\/,\r\n            winrt::hstring const&amp; \/*language*\/)\r\n    {\r\n        throw winrt::hresult_not_implemented();\r\n    }\r\n};\r\n<\/pre>\n<p>The Microsoft Visual C++ compiler doesn&#8217;t support this feature yet, but other compilers do, and they include the custom message in the primary error text.<\/p>\n<pre style=\"white-space: pre-wrap;\">\/\/ clang\r\nerror: attempt to use a deleted function: <span style=\"border: solid 1px currentcolor;\">If you derive from OneWayConverter, you must implement Convert()<\/span>\r\n\r\n\/\/ gcc\r\nerror: use of deleted function 'winrt::<wbr \/>Windows::<wbr \/>Foundation::<wbr \/>IInspectable OneWayConverter::<wbr \/>Convert(<wbr \/>winrt::<wbr \/>Windows::<wbr \/>Foundation::<wbr \/>IInspectable const&amp;,<wbr \/>winrt::<wbr \/>Windows::<wbr \/>UI::<wbr \/>Xaml::<wbr \/>Interop::<wbr \/>TypeName const&amp;,<wbr \/>winrt::<wbr \/>Windows::<wbr \/>Foundation::<wbr \/>IInspectable const&amp;,<wbr \/>winrt::<wbr \/>hstring const&amp;)': <span style=\"border: solid 1px currentcolor;\">If you derive from OneWayConverter, you must implement Convert()<\/span>\r\n<\/pre>\n<p>While this works for C++\/WinRT and plain C++, it doesn&#8217;t work for C++\/WRL because WRL derives from the abstract base class, and you cannot delete a method implemented by a base class. (Presumably because the method is still callable by casting to the base class.)<\/p>\n<p>So the <code>delete<\/code> trick works only if your declaration is not an override of a base class declaration.<\/p>\n<p>Tweaking the implementation to provide better compiler error messages is another example of <a title=\"Compiler error message metaprogramming: Helping to find the conflicting macro definition\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20211206-00\/?p=106002\"> compiler error message metaprogramming<\/a>, which is one of the under-appreciated aspects of authoring a code library.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Explicitly denying that you implement the method.<\/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-112654","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Explicitly denying that you implement the method.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112654","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=112654"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112654\/revisions"}],"predecessor-version":[{"id":112655,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112654\/revisions\/112655"}],"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=112654"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112654"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112654"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}