{"id":108318,"date":"2023-06-09T07:00:00","date_gmt":"2023-06-09T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108318"},"modified":"2023-06-09T13:39:15","modified_gmt":"2023-06-09T20:39:15","slug":"20230609-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230609-00\/?p=108318","title":{"rendered":"Reordering C++ template type parameters for usability purposes, and type deduction from the future"},"content":{"rendered":"<p>Suppose you want to write a function that takes any iterable and converts it to a <code>std::vector<\/code>. This is sort of like the C# analogue to the <code>.ToList()<\/code> LINQ method. Here&#8217;s a starter kit:<\/p>\n<pre>template&lt;typename Container&gt;\r\nauto to_vector(Container&amp;&amp; c)\r\n{\r\n    using ElementType = std::decay_t&lt;decltype(*c.begin())&gt;;\r\n    std::vector&lt;ElementType&gt; v;\r\n    std::copy(c.begin(), c.end(), std::back_inserter(v));\r\n    return v;\r\n}\r\n<\/pre>\n<p>This deduces the underlying value type of the vector from the original container, and it uses the default allocator. The <code>std::<wbr \/>decay_t<\/code> does multiple things for us here: It removes the references from the dereferenced iterator, and it also removes the <code>const<\/code> and <code>volatile<\/code> attributes. That second step allows a container of <code>const T<\/code> to convert to a vector of <code>T<\/code>.<\/p>\n<p>But maybe you want to let the caller override the underlying value type. For example, given a <code>std::list&lt;int&gt;<\/code>, you want to produce a <code>std::vector&lt;long&gt;<\/code> by saying<\/p>\n<pre>std::list&lt;int&gt; l = \/* some expression *\/;\r\nauto v = to_vector&lt;long&gt;(l);\r\n<\/pre>\n<p>Okay, so you add an <code>ElementType<\/code> type parameter to the template function, and have it default to the container&#8217;s underlying type.<\/p>\n<pre>template&lt;typename Container,\r\n    typename ElementType =\r\n        std::decay_t&lt;decltype(*std::declval&lt;Container&gt;().begin())&gt;&gt;\r\nauto to_vector(Container&amp;&amp; c)\r\n{\r\n    std::vector&lt;ElementType&gt; v;\r\n    std::copy(c.begin(), c.end(), std::back_inserter(v));\r\n    return v;\r\n}\r\n<\/pre>\n<p>This works great, but using it is an annoyance because in order to customize the <code>Element\u00adType<\/code>, you have to restate the <code>Container<\/code> first.<\/p>\n<pre>std::list&lt;int&gt; l = \/* some expression *\/;\r\nauto v = to_vector&lt;<span style=\"border: solid 1px currentcolor;\">std::list&lt;int&gt;&amp;<\/span>, <span style=\"border: solid 1px currentcolor;\">long<\/span>&gt;(l);\r\n<\/pre>\n<p>You&#8217;d much rather say<\/p>\n<pre>std::list&lt;int&gt; l = \/* some expression *\/;\r\nauto v = to_vector&lt;<span style=\"border: solid 1px currentcolor;\">long<\/span>&gt;(l);\r\n<\/pre>\n<p>This reads much nicer because it looks like you&#8217;re saying &#8220;I&#8217;m converting to <code>vector&lt;long&gt;<\/code>.&#8221; So you swap the order of the type parameters:<\/p>\n<pre>template&lt;\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">typename ElementType =                                         <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    std::decay_t&lt;decltype(*std::declval&lt;Container&gt;().begin())&gt;,<\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">typename Container&gt;                                            <\/span>\r\nauto to_vector(Container&amp;&amp; c)\r\n{\r\n    std::vector&lt;ElementType&gt; v;\r\n    std::copy(c.begin(), c.end(), std::back_inserter(v));\r\n    return v;\r\n}\r\n<\/pre>\n<p>Unfortunately, this doesn&#8217;t compile because you are trying to default <code>ElementType<\/code> based on a <code>Container<\/code> that hasn&#8217;t been declared yet.<\/p>\n<p>So how can you get a template type parameter to default to a value that is dependent upon a future type parameter?<\/p>\n<p>You use a trick.<\/p>\n<pre>template&lt;\r\n    <span style=\"border: solid 1px currentcolor;\">typename ElementType = void,<\/span>\r\n    typename Container&gt;\r\nauto to_vector(Container&amp;&amp; c)\r\n{\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">using ActualElementType = std::conditional_t&lt;<\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    std::is_same_v&lt;ElementType, void&gt;,       <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    std::decay_t&lt;decltype(*c.begin())&gt;,      <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">    ElementType&gt;;                            <\/span>\r\n    std::vector&lt;ActualElementType&gt; v;\r\n    std::copy(c.begin(), c.end(), std::back_inserter(v));\r\n    return v;\r\n}\r\n<\/pre>\n<p>The formal type parameter <code>ElementType<\/code> defaults to <code>void<\/code>, which is a sentinel value that means &#8220;Substitute the underlying value type of the collection here.&#8221; Inside the function body, after we have successfully deduced the <code>Container<\/code>, we calculate the <code>Actual\u00adElement\u00adType<\/code> by using the explicitly-provided <code>Element\u00adType<\/code> or (if it was defaulted to <code>void<\/code>) using the underlying type of the <code>Container<\/code>.<\/p>\n<p>Adding support for custom allocators is more complicated because the allocator is a parameter. We have to convert the <code>void<\/code> to an actual allocator by the time we get to the parameter list.<\/p>\n<pre>template&lt;\r\n    typename ElementType = void,\r\n    <span style=\"border: solid 1px currentcolor;\">typename Allocator = void,<\/span>\r\n    typename Container&gt;\r\nauto to_vector(Container&amp;&amp; c,\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">std::conditional_t&lt;                                                <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    std::is_same_v&lt;Allocator, void&gt;,                               <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    std::allocator&lt;                                                <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        std::conditional_t&lt;                                        <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        std::is_same_v&lt;ElementType, void&gt;,                         <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        std::decay_t&lt;decltype(*std::declval&lt;Container&gt;().begin())&gt;,<\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        ElementType&gt;&gt;,                                             <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">    Allocator&gt; al = {})                                            <\/span>\r\n{\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">using ActualElementType = std::conditional_t&lt;                      <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    std::is_same_v&lt;ElementType, void&gt;,                             <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    std::decay_t&lt;decltype(*std::declval&lt;Container&gt;().begin())&gt;,    <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    ElementType&gt;;                                                  <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">using ActualAllocator = std::conditional_t&lt;                        <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    std::is_same_v&lt;Allocator, void&gt;,                               <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    std::allocator&lt;ActualElementType&gt;,                             <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">    Allocator&gt;;                                                    <\/span>\r\n    std::vector&lt;ActualElementType, <span style=\"border: solid 1px currentcolor;\">ActualAllocator<\/span>&gt; v<span style=\"border: solid 1px currentcolor;\">(al)<\/span>;\r\n    std::copy(c.begin(), c.end(), std::back_inserter(v));\r\n    return v;\r\n}\r\n<\/pre>\n<p>There&#8217;s a lot of repetition here: We want to use <code>ActualAllocator<\/code> defined in the function body, but we can&#8217;t use it in the function prototype, so we have to inline its definition, producing a big ugly mess.<\/p>\n<p>One way to solve this is to &#8220;save&#8221; it in another defaulted template type parameter.<\/p>\n<pre>template&lt;\r\n    typename ElementType = void,\r\n    <span style=\"border: solid 1px currentcolor;\">typename Allocator = void,<\/span>\r\n    typename Container,\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">typename ActualElementType = std::conditional_t&lt;                   <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    std::is_same_v&lt;ElementType, void&gt;,                             <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    std::decay_t&lt;decltype(*std::declval&lt;Container&gt;().begin())&gt;,    <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    ElementType&gt;;                                                  <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">typename ActualAllocator = std::conditional_t&lt;                     <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    std::is_same_v&lt;Allocator, void&gt;,                               <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    std::allocator&lt;ActualElementType&gt;,                             <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">    Allocator&gt;&gt;                                                    <\/span>\r\nauto to_vector(Container&amp;&amp; c,\r\n    <span style=\"border: solid 1px currentcolor;\">ActualAllocator al = ActualAllocator()<\/span>)\r\n{\r\n    std::vector&lt;ActualElementType, <span style=\"border: solid 1px currentcolor;\">ActualAllocator<\/span>&gt; v<span style=\"border: solid 1px currentcolor;\">(al)<\/span>;\r\n    std::copy(c.begin(), c.end(), std::back_inserter(v));\r\n    return v;\r\n}\r\n<\/pre>\n<p>Another solution is to create helper types to do the calculations.<\/p>\n<pre>template&lt;typename ElementType, typename Container&gt;\r\nusing ActualElementType = std::conditional_t&lt;\r\n        std::is_same_v&lt;ElementType, void&gt;,\r\n        std::decay_t&lt;decltype(*std::declval&lt;Container&gt;().begin())&gt;,\r\n        ElementType&gt;;\r\n\r\ntemplate&lt;typename ActualElementType,\r\n    typename ActualAllocator = std::conditional_t&lt;\r\n        std::is_same_v&lt;Allocator, void&gt;,\r\n        std::allocator&lt;ActualElementType&lt;ElementType, Container&gt;&gt;,\r\n        Allocator&gt;;\r\n\r\ntemplate&lt;\r\n    typename ElementType = void,\r\n    typename Allocator = void,\r\n    typename Container&gt;\r\nauto to_vector(Container&amp;&amp; c,\r\n    <span style=\"border: solid 1px currentcolor;\">ActualAllocator&lt;ElementType, Allocator, Container&gt;<\/span> al = {})\r\n{\r\n    std::vector&lt;<span style=\"border: solid 1px currentcolor;\">ActualElementType&lt;ElementType, Container&gt;,<\/span>\r\n        <span style=\"border: solid 1px currentcolor;\">ActualAllocator&lt;ElementType, Allocator, Container&gt;<\/span>&gt; v(al);\r\n    std::copy(c.begin(), c.end(), std::back_inserter(v));\r\n    return v;\r\n}\r\n<\/pre>\n<p><b>Bonus chatter<\/b>: There&#8217;s still more work to be done with <code>to_vector<\/code>, since it doesn&#8217;t work with <code>vector&lt;bool&gt;<\/code>, thanks to <code>vector&lt;bool&gt;<\/code>&#8216;s wacko proxy object, and it doesn&#8217;t work with C-style arrays since they don&#8217;t have a <code>begin()<\/code> method.<\/p>\n<p>Instead, we can use <code>std::iterator_traits<\/code> to tell us what the iterator produces. and we can use <code>std::begin<\/code> to support C-style arrays.<\/p>\n<pre>template&lt;typename ElementType, typename Container&gt;\r\nusing ActualElementType = std::conditional_t&lt;\r\n        std::is_same_v&lt;ElementType, void&gt;,\r\n        std::decay_t&lt;\r\n            <span style=\"border: solid 1px currentcolor; border-bottom: none;\">std::iterator_traits&lt;                             <\/span>\r\n            <span style=\"border: 1px currentcolor; border-style: none solid;\">        decltype(                                 <\/span>\r\n            <span style=\"border: 1px currentcolor; border-style: none solid;\">            std::begin(std::declval&lt;Container&gt;()))<\/span>\r\n            <span style=\"border: solid 1px currentcolor; border-top: none;\">     &gt;::value_type&gt;,                              <\/span>\r\n        ElementType&gt;;\r\n\r\ntemplate&lt;typename ActualElementType,\r\n    typename ActualAllocator = std::conditional_t&lt;\r\n        std::is_same_v&lt;Allocator, void&gt;,\r\n        std::allocator&lt;ActualElementType&lt;ElementType, Container&gt;&gt;,\r\n        Allocator&gt;;\r\n\r\ntemplate&lt;\r\n    typename ElementType = void,\r\n    typename Allocator = void,\r\n    typename Container&gt;\r\nauto to_vector(Container&amp;&amp; c,\r\n    ActualAllocator&lt;ElementType, Allocator, Container&gt; al = {})\r\n{\r\n    std::vector&lt;ActualElementType&lt;ElementType, Container&gt;,\r\n        ActualAllocator&lt;ElementType, Allocator, Container&gt;&gt; v(al);\r\n    std::copy(c.begin(), c.end(), std::back_inserter(v));\r\n    return v;\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>You want them to go first, but you also want to deduce them.<\/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-108318","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>You want them to go first, but you also want to deduce them.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108318","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=108318"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108318\/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=108318"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108318"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108318"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}