{"id":110681,"date":"2024-12-27T07:00:00","date_gmt":"2024-12-27T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=110681"},"modified":"2025-01-03T11:53:33","modified_gmt":"2025-01-03T19:53:33","slug":"20241227-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20241227-00\/?p=110681","title":{"rendered":"In C++, failure to meet the requirements does not always mean that you fail if you don&#8217;t meet the requirements"},"content":{"rendered":"<p>I was asked to help debug a problem where a pointer passed to a function was received slightly offset from the original value. The pointer is passed as an opaque parameter and is supposed to be spit out the other end of a pipeline. But somehow, the value that came out the other end was slightly different.<\/p>\n<p>For expository purposes, let&#8217;s say that the underlying function we&#8217;re trying to call is this one.<\/p>\n<pre>void SubmitWork(HWORKER worker, int commandCode, void* param);\r\n<\/pre>\n<p>You call <code>Submit\u00adWork<\/code> with a worker, a command code, and a raw pointer to some data that depends on the command code.<\/p>\n<p>The developer sent me some debugging output that showed that the function that handled the command code received a different pointer from what was passed. One possibility is that there was a bug in the library that resulted in it unintentionally modifying the final pointer parameter, but this seemed unlikely, since the value should be opaque to the library. I was able to get on a video call with the developer to watch them step through the code, and that&#8217;s where I noticed something interesting.<\/p>\n<p>The developer&#8217;s code didn&#8217;t actually call <code>Submit\u00adWork<\/code>, but rather used a wrapper that in turn called <code>Submit\u00adWork<\/code>:<\/p>\n<pre>class Worker\r\n{\r\npublic:\r\n    void SubmitWork(int commandCode, void* param)\r\n    {\r\n        ::SubmitWork(m_worker, commandCode, param);\r\n    }\r\n\r\n    template&lt;typename T&gt;\r\n    void SubmitWork(int commandCode, T&amp; param)\r\n        requires std::is_standard_layout_v&lt;T&gt;\r\n    {\r\n        \/\/ Delegate to the void* version.\r\n        SubmitWork(commandCode, std::addressof(param));\r\n    }\r\nprivate:\r\n    HWORKER m_worker;\r\n};\r\n<\/pre>\n<p>The wrapper is trying to make the <code>Submit\u00adWork()<\/code> function a little easier to use by letting you pass a standard layout type as the second parameter, and it will pass it by address. The idea is that in addition to passing a raw pointer, you can also pass a structure like this:<\/p>\n<pre>struct SetSizeInfo\r\n{\r\n    uint64_t itemId;\r\n    uint64_t size;\r\n};\r\nSetSizeInfo info;\r\ninfo.itemId = 31415;\r\ninfo.size = 64;\r\nworker.SubmitWork(CMD_SETSIZE, info);\r\n<\/pre>\n<p>and it turns into<\/p>\n<pre>::SubmitWork(m_hWorker, CMD_SETSIZE, &amp;info);\r\n<\/pre>\n<p>with an extra check that <code>info<\/code> is a standard layout type (can be <code>memcpy<\/code>&#8216;d).<\/p>\n<p>But it turns out that this helper isn&#8217;t so helpful.<\/p>\n<p>Suppose you had someone who called the method the old-fashioned pointer way:<\/p>\n<pre>char* name = allocate_name();\r\nworker.SubmitWork(CMD_SETNAME, name);\r\n<\/pre>\n<p>The compiler sees that both of the overloads of the <code>Submit\u00adWork<\/code> method are in play.<\/p>\n<pre>\/\/ Using conversion from char*&amp; to void*\r\nworker.SubmitWork(int, void*);\r\n\r\n\/\/ Treating the char* as a standard layout type.\r\nworker.SubmitWork&lt;char*&gt;(int, char*&amp;);\r\n<\/pre>\n<p>The second overload is in play because pointers are standard layout types.<\/p>\n<p>And the second overload wins because <code>char*&amp;<\/code> is a better match for <code>char*&amp;<\/code> than <code>void*<\/code>!<\/p>\n<p>This means that instead of passing the pointer through to <code>::Submit\u00adWork()<\/code>, the templated helper function passes the <i>address of<\/i> the pointer to <code>::Submit\u00adWork()<\/code>.<\/p>\n<p>And that&#8217;s why the pointer arrives incorrectly when the work is processed: The pointer that was passed to <code>::Submit\u00adWork<\/code> was correctly passed through to the processor. The problem is that the wrong pointer was passed to <code>::Submit\u00adWork<\/code> in the first place!<\/p>\n<p>One way to fix this is to make pointers ineligible for the template function.<\/p>\n<pre>    template&lt;typename T&gt;\r\n    void SubmitWork(int commandCode, T&amp; param)\r\n        requires std::is_standard_layout_v&lt;T&gt;\r\n        &amp;&amp; (!std::is_pointer_v&lt;T&gt;)\r\n    {\r\n        \/\/ Delegate to the void* version.\r\n        SubmitWork(commandCode, std::addressof(param));\r\n    }\r\n<\/pre>\n<p>However, this approach still has a hole:<\/p>\n<pre>std::string name(\"hello\");\r\nworker.SubmitWork(CMD_SETNAME, &amp;name);\r\n<\/pre>\n<p>Here, I am passing a pointer to a non-standard-layout type, and since the template function is not eligible for pointers, the only remaining overload is the one that takes a <code>void*<\/code>, which <code>std::string*<\/code> happily converts to.<\/p>\n<p>The helpers were trying to make sure that only pointers to standard layout types are passed to <code>::Submit\u00adWork<\/code>, but I was able to sneak a <code>std::string*<\/code> into <code>::Submit\u00adWork<\/code> because the template function that did the enforcement <i>removed itself from consideration<\/i> rather than accepting the parameter and then complaining about it.<\/p>\n<p>In C++, if the compiler is unable to satisfy the constraints of a function, the function is simply <i>ignored<\/i> and not considered for overload resolution. You sort of knew that: Suppose you have two overloads of the <code>do_something()<\/code> function:<\/p>\n<pre>void do_something(char* p);\r\nvoid do_something(int v);\r\n<\/pre>\n<p>When you write <code>do_something(42)<\/code>, the first overload is ignored because <code>42<\/code> cannot be converted to <code>char*<\/code>, and the second overload is used because <code>42<\/code> can indeed be converted to <code>int<\/code>. You did not expect to get an error from the <code>char*<\/code> overload.<\/p>\n<p>But in our case with <code>Submit\u00adWork()<\/code>, if you call it with a pointer to a non-standard-layout type, we don&#8217;t want the template version to be ignored. We want it to trigger an error! But it doesn&#8217;t, because it is a failed substitution, and as we all know, Substitution Failure Is Not An Error (SFINAE). What we want to do is to allow the substitution to succeed, but then generate an error in the function body if the type is not standard layout.<\/p>\n<pre>    \/\/ Basic version, no extra checking.\r\n    void SubmitWork(int commandCode, void* param)\r\n    {\r\n        ::SubmitWork(m_worker, commandCode, param);\r\n    }\r\n\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">\/\/ This version matches all non-void pointers.     <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">template&lt;typename T&gt;                               <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">void SubmitWork(int commandCode, T* param)         <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">{                                                  <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    static_assert(std::is_standard_layout_v&lt;T&gt;,    <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        \"T must be a standard layout type\");       <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    \/\/ Delegate to the void* version.              <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    SubmitWork(commandCode,                        <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        static_cast&lt;void*&gt;(param));                <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">}                                                  <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0                                                  <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">\/\/ This overload matches all references.           <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">template&lt;typename T&gt;                               <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">void SubmitWork(int commandCode, T&amp; param)         <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">{                                                  <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    \/\/ Delegate to the T* version.                 <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    SubmitWork(commandCode, std::addressof(param));<\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                                  <\/span>\r\n<\/pre>\n<p>In my opinion, taking a reference just adds to the confusion. I took the solution a step further and got rid of the reference overload. Everybody just calls with the pointer they want to submit.<\/p>\n<pre>    \/\/ Basic version, no extra checking.\r\n    void SubmitWork(int commandCode, void* param)\r\n    {\r\n        ::SubmitWork(m_worker, commandCode, param);\r\n    }\r\n\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">\/\/ This version matches all non-void pointers.     <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">template&lt;typename T&gt;                               <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">void SubmitWork(int commandCode, T* param)         <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">{                                                  <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    static_assert(std::is_standard_layout_v&lt;T&gt;,    <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        \"T must be a standard layout type\");       <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    \/\/ Delegate to the void* version.              <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    SubmitWork(commandCode,                        <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        static_cast&lt;void*&gt;(param));                <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                                  <\/span>\r\n\r\n    \/\/ <span style=\"text-decoration: line-through;\">This overload matches all references.<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">template&lt;typename T&gt;<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">void SubmitWork(int commandCode, T&amp; param)<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">{<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">    \/\/ Delegate to the T* version.<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">    SubmitWork(commandCode, std::addressof(param));<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">}<\/span>\r\n<\/pre>\n<p>This avoids confusion over whether passing a pointer means &#8220;I&#8217;m calling it with the pointer I want&#8221;, or whether it means &#8220;I&#8217;m calling it with a standard layout type that happens to be a pointer!&#8221; (so please pass a pointer to it).<\/p>\n<p>In fact, you can go even further and get rid of the overload!<\/p>\n<pre>    \/\/ <span style=\"text-decoration: line-through;\">Basic version, no extra checking.<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">void SubmitWork(int commandCode, void* param)<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">{<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">    ::SubmitWork(m_worker, commandCode, param);<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">}<\/span>\r\n\r\n    template&lt;typename T&gt;\r\n    void SubmitWork(int commandCode, T* param)\r\n    {\r\n        static_assert(<span style=\"border: solid 1px currentcolor; border-bottom: none;\">std::is_same_v&lt;T, void&gt; ||   <\/span>\r\n                      <span style=\"border: solid 1px currentcolor; border-top: none;\">std::is_standard_layout_v&lt;T&gt;,<\/span>\r\n            \"T must be a standard layout type\");\r\n        ::SubmitWork(m_worker, commandCode, param));\r\n    }\r\n<\/pre>\n<p>Now there is no confusion over which overload you are calling because there is no longer an overload.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Combining SFINAE\/requires, standard layout, and overload resolution.<\/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-110681","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Combining SFINAE\/requires, standard layout, and overload resolution.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/110681","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=110681"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/110681\/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=110681"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=110681"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=110681"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}