{"id":109940,"date":"2024-06-27T07:00:00","date_gmt":"2024-06-27T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109940"},"modified":"2024-06-24T08:35:47","modified_gmt":"2024-06-24T15:35:47","slug":"20240627-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240627-00\/?p=109940","title":{"rendered":"Writing a <CODE>remove_all_pointers<\/CODE> type trait, part 1"},"content":{"rendered":"<p>There is a <code>std::<wbr \/>remove_<wbr \/>pointer<\/code> type trait helper. If you give it a pointer type <code>T*<\/code>, its <code>type<\/code> member type is <code>T<\/code>. Otherwise, the <code>type<\/code> member type is just the template type unchanged.<\/p>\n<p>But what if you want to remove <i>all<\/i> pointers? For example, <code>remove_<wbr \/>all_<wbr \/>pointers&lt;int*const*volatile*&gt;::type<\/code> should be <code>int<\/code>.<\/p>\n<p>You can define this as a recursive operation. In pseudo-code:<\/p>\n<pre>template&lt;typename T&gt;\r\nauto remove_all_pointers\r\n{\r\n    if (std::is_pointer_v&lt;T&gt;) {\r\n        return remove_all_pointers&lt;\r\n            std::remove_pointer_t&lt;T&gt;\r\n        &gt;;\r\n    } else {\r\n        return T;\r\n    }\r\n}\r\n<\/pre>\n<p>One way to express conditional evaluation in template metaprogramming is to use <code>std::conditional&lt;a, b, c&gt;::type<\/code>, which is <code>b<\/code> if <code>a<\/code> is <code>true<\/code> and is <code>c<\/code> if <code>a<\/code> is <code>false<\/code>.<\/p>\n<p>Therefore, your first attempt might be to write it as a one-liner built out of <code>std::conditional<\/code>.<\/p>\n<pre>template&lt;typename T&gt;\r\nusing remove_all_pointers_t =\r\n    std::conditional_t&lt;\r\n        std::is_pointer_v&lt;T&gt;,\r\n        remove_all_pointers_t&lt;\r\n            std::remove_pointer_t&lt;T&gt;&amp;gt,;\r\n        T&gt;;\r\n<\/pre>\n<p>Okay, this doesn&#8217;t work because of the recursive reference to <code>remove_<wbr \/>all_<wbr \/>pointers_t<\/code> before it has completed its declaration. We can sidestep this by using a <code>struct<\/code>.<\/p>\n<pre>template&lt;typename T&gt;\r\nstruct remove_all_pointers\r\n{\r\n  using type = std::conditional_t&lt;\r\n        std::is_pointer_v&lt;T&gt;,\r\n        typename remove_all_pointers&lt;\r\n            std::remove_pointer_t&lt;T&gt;&gt;::type,\r\n        T&gt;;\r\n};\r\n<\/pre>\n<p>This compiles, but you get an error when you try to use it:<\/p>\n<pre style=\"white-space: pre-wrap;\">using test = remove_all_pointers&lt;int*const*volatile*&gt;::type;\r\n\r\n\/\/ gcc\r\nIn instantiation of 'struct remove_all_pointers&lt;int&gt;':\r\n    recursively required from 'struct remove_all_pointers&lt;int* const* volatile&gt;'\r\n    required from 'struct remove_all_pointers&lt;int* const* volatile*&gt;'\r\n    required from here\r\nerror: invalid use of incomplete type 'struct remove_all_pointers&lt;int&gt;'\r\n    |   using type = std::conditional_t&lt;\r\n    |         ^~~~\r\nnote: definition of 'struct remove_all_pointers&lt;int&gt;' is not complete until the closing brace\r\n    | struct remove_all_pointers\r\n    |        ^~~~~~~~~~~~~~~~~~~\r\n\r\n\/\/ clang\r\nerror: no type named 'type' in 'remove_all_pointers&lt;int&gt;'\r\n    |         typename remove_all_pointers&lt;\r\n    |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\n    |             std::remove_pointer_t&lt;T&gt;&gt;::type,\r\n    |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~\r\nnote: in instantiation of template class 'remove_all_pointers&lt;int&gt;' requested here\r\n    |         typename remove_all_pointers&lt;\r\n    |                  ^\r\nnote: in instantiation of template class 'remove_all_pointers&lt;int *const&gt;' requested here\r\nnote: in instantiation of template class 'remove_all_pointers&lt;int *const *volatile&gt;' requested here\r\nnote: in instantiation of template class 'remove_all_pointers&lt;int *const *volatile *&gt;' requested here\r\n   | using test = remove_all_pointers&lt;int*const*volatile*&gt;::type;\r\n   |              ^\r\n\r\n\/\/ msvc\r\nerror C2146: syntax error: missing '&gt;' before identifier 'type'\r\nnote: the template instantiation context (the oldest one first) is\r\nnote: see reference to class template instantiation 'remove_all_pointers&lt;int *const *volatile *&gt;' being compiled\r\nnote: see reference to class template instantiation 'remove_all_pointers&lt;int *const *volatile &gt;' being compiled\r\nnote: see reference to class template instantiation 'remove_all_pointers&lt;int *const &gt;' being compiled\r\nnote: see reference to class template instantiation 'remove_all_pointers&lt;int&gt;' being compiled\r\n<\/pre>\n<p>Okay, maybe we were too ambitious.<\/p>\n<p>All the error messages show that the template was able to recurse and strip away pointers, but then it ran into a problem when it reached the base case. Let&#8217;s look at that base case:<\/p>\n<pre>struct remove_all_pointers&lt;int&gt;\r\n{\r\n  using type = std::conditional_t&lt;\r\n        std::is_pointer_v&lt;int&gt;,\r\n        remove_all_pointers&lt;\r\n            std::remove_pointer_t&lt;int&gt;&gt;::type,\r\n        int&gt;;\r\n};\r\n<\/pre>\n<p>After substituting <code>std::remove_pointer_t&lt;int&gt; = int<\/code>, we get<\/p>\n<pre>struct remove_all_pointers&lt;int&gt;\r\n{\r\n  using type = std::conditional_t&lt;\r\n        std::is_pointer_v&lt;int&gt;,\r\n        <span style=\"border: solid 1px currentcolor;\">remove_all_pointers&lt;int&gt;::type<\/span>,\r\n        int&gt;;\r\n};\r\n<\/pre>\n<p>Now we see the problem. The definition of <code>remove_<wbr \/>all_<wbr \/>pointers&lt;int&gt;::<wbr \/>type<\/code> is dependent on itself.<\/p>\n<p>The catch here is that <code>std::conditional<\/code> is not a short-circuiting operator. How can it be? It&#8217;s a template!<\/p>\n<p>In order to instantiate a template, the compiler first evaluates the template parameters, and then it looks at the template expansion that results. The compiler doesn&#8217;t &#8220;look ahead&#8221; and say, &#8220;Oh, I can tell that the template expansion never uses its second parameter, so I will skip the evaluation of the second parameter.&#8221;\u00b9<\/p>\n<p>One way to solve this problem is to move the expansion of the two parameters to a partial specialization. That way, only the pointer cases invoke the template recursively.<\/p>\n<pre>template&lt;typename T,\r\n    bool = std::is_pointer_v&lt;T&gt;&gt;\r\nstruct remove_all_pointers;\r\n\r\ntemplate&lt;typename T&gt;\r\nstruct remove_all_pointers&lt;T, false&gt;\r\n{\r\n    using type = T;\r\n};\r\n\r\ntemplate&lt;typename T&gt;\r\nstruct remove_all_pointers&lt;T, true&gt;\r\n{\r\n    using type = typename remove_all_pointers&lt;\r\n        std::remove_pointer_t&lt;T&gt;&gt;::type;\r\n};\r\n<\/pre>\n<p>We add a hidden second template parameter which defaults to <code>std::is_pointer_v&lt;T&gt;<\/code>. We then partially specialize the template on that second template parameter: If it&#8217;s <code>false<\/code> (<code>T<\/code> is not a pointer), then the type is <code>T<\/code> itself, which provides our base case (no longer accidentally referring to itself). If it&#8217;s <code>true<\/code> (<code>T<\/code> is a pointer), then the type is calculated recursively after stripping away one layer of indirection.<\/p>\n<pre>template&lt;typename T&gt;\r\nusing remove_all_pointers_t =\r\n    typename remove_all_pointers&lt;T&gt;::type;\r\n\r\nstatic_assert(std::is_same_v&lt;\r\n    remove_all_pointers_t&lt;int*const*volatile*&gt;,\r\n    int&gt;);\r\n<\/pre>\n<p>As a small tuning step, we can fold the base case into the initial definition, so that only the recursive case is a partial specialization.<\/p>\n<pre>template&lt;typename T,\r\n    bool = std::is_pointer_v&lt;T&gt;&gt;\r\n<span style=\"border: solid 1px currentcolor; border-bottom: none;\">struct remove_all_pointers<\/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 = T;       <\/span>\r\n<span style=\"border: solid 1px currentcolor; border-top: none;\">};                        <\/span>\r\n\r\ntemplate&lt;typename T&gt;\r\nstruct remove_all_pointers&lt;T, true&gt;\r\n{\r\n    using type = typename remove_all_pointers&lt;\r\n        std::remove_pointer_t&lt;T&gt;&gt;::type;\r\n};\r\n<\/pre>\n<p>Are we done?<\/p>\n<p>No, not yet.<\/p>\n<p>We&#8217;ll continue next time.<\/p>\n<p>\u00b9 Indeed, the &#8220;I evaluate all the parameters even if they aren&#8217;t used&#8221; behavior is one of the things that SFINAE relies on!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Delaying the expansion to avoid infinite recursion.<\/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-109940","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Delaying the expansion to avoid infinite recursion.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109940","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=109940"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109940\/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=109940"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109940"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109940"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}