{"id":108259,"date":"2023-05-29T07:00:00","date_gmt":"2023-05-29T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108259"},"modified":"2023-05-29T18:19:50","modified_gmt":"2023-05-30T01:19:50","slug":"20230529-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230529-00\/?p=108259","title":{"rendered":"On writing functions that accept any specialization of a C++ template type"},"content":{"rendered":"<p>Suppose you want to write a template function that accepts any specialization of <code>std::vector<\/code>. Your first try would probably be something like this:<\/p>\n<pre>template&lt;typename Value&gt;\r\nvoid accept_any_vector(std::vector&lt;Value&gt; v);\r\n<\/pre>\n<p>However, this does not actually accept any vector specialization. There is a second template argument for the allocator, which has a default value that nearly everyone uses. But if somebody has a vector with a custom allocator, then your function won&#8217;t match.<\/p>\n<p>Okay, so add an allocator, too.<\/p>\n<pre>template&lt;typename Value, typename Allocator&gt;\r\nvoid accept_any_vector(std::vector&lt;Value, Allocator&gt; v);\r\n<\/pre>\n<p>This works today, but it may not work tomorrow.<\/p>\n<p>A future version of <code>std::vector<\/code> <a href=\"https:\/\/www.open-std.org\/jtc1\/sc22\/wg21\/docs\/papers\/2018\/p0921r2.pdf\"> might add new template arguments<\/a>, provided that they have suitable defaults that preserve existing behavior.\u00b9 We don&#8217;t want that future version to break us, so we should slurp up all the template arguments that exist:<\/p>\n<pre>template&lt;typename...Args&gt;\r\nvoid accept_any_vector(std::vector&lt;Args...&gt; v);\r\n<\/pre>\n<p>Now, once we&#8217;ve accepted any kind of vector, we have lost the template parameters that name the value type and allocator. You might think you could rescue them by naming them and putting all the future nonsense in the variadic portion:<\/p>\n<pre>template&lt;typename Value, typename Allocator, typename...Extra&gt;\r\nvoid accept_any_vector(std::vector&lt;Value, Allocator, Extra...&gt; v);\r\n<\/pre>\n<p>However, this doesn&#8217;t work:<\/p>\n<pre style=\"white-space: pre-wrap;\">\/\/ msvc\r\nerror C2977: 'std::vector': too many template arguments\r\n\r\n\/\/ clang\r\nerror: too many template arguments for class template 'vector'\r\n\r\n\/\/ icc\r\nerror: too many arguments for class template \"std::vector\"\r\n\r\n\/\/ gcc\r\n(no errors)\r\n<\/pre>\n<p>Fortunately, you can recover the <code>Value<\/code> and <code>Allocator<\/code> from <code>std::vector&lt;Args...&gt;<\/code> because <code>std::vector<\/code> lets you access the underlying type and allocator through member types.<\/p>\n<pre>template&lt;typename...Args&gt;\r\nvoid accept_any_vector(std::vector&lt;Args...&gt; v)\r\n{\r\n    using vector = std::vector&lt;Args...&gt;;\r\n    using Value = typename vector::value_type;\r\n    using Allocator = typename vector::allocator_type;\r\n\r\n    ...\r\n}\r\n<\/pre>\n<p>C++ standard library types generally provide these member types to allow you to recover the template type arguments from the type. Other libraries are hit or miss.<\/p>\n<p>\u00b9 The standard also permits functions to accept default parameters or have additional default template arguments.\u00b2 This is used primarily for SFINAE purposes, so that some overloads become removed from consideration if particular requirements are not met. The standard has the concept of &#8220;addressable functions&#8221;, which are the functions that the standard guarantees will never be overloaded or have a signature different from the one printed in the standard.<\/p>\n<p>\u00b2 We ran afoul of this issue in an April Fool&#8217;s article about <a title=\"The std::invoke function does more than invoke functions\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220401-00\/?p=106426\"> invoke-oriented programming<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>There are the obvious arguments, the non-obvious arguments, and the invisible arguments.<\/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-108259","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>There are the obvious arguments, the non-obvious arguments, and the invisible arguments.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108259","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=108259"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108259\/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=108259"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108259"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108259"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}