
{"id":4865,"date":"2023-07-20T02:10:15","date_gmt":"2023-07-20T06:10:15","guid":{"rendered":"https:\/\/ikriv.com\/blog\/?p=4865"},"modified":"2023-07-20T02:10:15","modified_gmt":"2023-07-20T06:10:15","slug":"c-stdfunction-type-and-templates","status":"publish","type":"post","link":"https:\/\/ikriv.com\/blog\/?p=4865","title":{"rendered":"C++ std::function type and templates"},"content":{"rendered":"<p>TL;DR <code>std::function<\/code> works great when we know exact types of the arguments and the return value. It breaks down miserably in presence of templates, and the error messages are not great, to put it mildly. I wasted quite a few minutes today because of this problem in our production code.<\/p>\n<p>Consider this example. In C++, lambda has an unspecified implementation-dependent type, but we can pass it to as a\u00a0<code>getKey<\/code> parameter, because <code>std::function<\/code> has a templated constructor that can take any callable type. The compiler knows what type is required, and performs implicit conversion for us.<\/p>\n<pre>#include &lt;string&gt;\r\n#include &lt;vector&gt;\r\n#include &lt;unordered_map&gt;\r\n#include &lt;functional&gt;\r\n#include &lt;iostream&gt;\r\n\r\nusing namespace std;\r\n\r\nauto to_map(vector&lt;string&gt; const&amp; items, <strong>function&lt;int(string const&amp;)&gt;<\/strong> getKey) {\r\n  unordered_map&lt;int, string&gt; map;\r\n  for (auto const&amp; item : items) {\r\n    map[getKey(item)] = item;\r\n  }\r\n  return map;\r\n}\r\n\r\nint main()\r\n{\r\n  vector&lt;string&gt; v = {\"a\", \"bb\", \"ccc\"};\r\n  auto map = to_map(v, [](auto const&amp; s) { return s.size(); });\r\n  cout&lt;&lt; map[3] &lt;&lt; endl; \/\/ prints 'ccc'\r\n  return 0;\r\n}<\/pre>\n<p>Unfortunately, it all breaks down if we switch to templates:<\/p>\n<pre>\/\/ includes skipped for brevity\r\n\r\ntemplate&lt;class TKey, class TValue&gt;\r\nauto to_map(vector&lt;TValue&gt; const&amp; items, function&lt;TKey(TValue const&amp;)&gt; getKey) {\r\n  unordered_map&lt;TKey, TValue&gt; map;\r\n  for (auto const&amp; item : items) {\r\n    map[getKey(item)] = item;\r\n  }\r\n  return map;\r\n}\r\n\r\nint main()\r\n{\r\n  vector&lt;string&gt; v = {\"a\", \"bb\", \"ccc\"};\r\n  \/\/ the line below does not compile with a crytpic message\r\n  \/\/ \u2018main()::&amp;)&gt;\u2019 is not derived from \u2018std::function\u2019\r\n  auto map = to_map(v, [](auto const&amp; s) { return s.size(); }); \/\/ not good\r\n\r\n  cout&lt;&lt; map[3] &lt;&lt; endl; \/\/ prints 'ccc'\r\n  return 0;\r\n}<\/pre>\n<p>The compiler no longer knows the exact type of the <code>getKey<\/code> parameter, so implicit conversion is not possible. Unlike languages like C#, in C++ a lambda is not a <code>function<\/code>, it is some hidden implementation-specific type. We can add an explicit conversion, but then we lose the benefit of automatic type deduction, and the code becomes even more verbose than usual:<\/p>\n<pre>function&lt;int(string const&amp;)&gt; getKey = [](auto const&amp; s) { return s.size(); };\r\nauto map = to_map(v, getKey);<\/pre>\n<p>Alternatively, we can make function type itself a template parameter:<\/p>\n<pre>\/\/ includes skipped for brevity\r\n\r\ntemplate&lt;class TGetKeyFunction, class TValue&gt;\r\nauto to_map(vector&lt;TValue&gt; const&amp; items, TGetKeyFunction getKey) {\r\n  using TKey = decltype(getKey(items[0]));\r\n  unordered_map&lt;TKey, TValue&gt; map;\r\n  for (auto const&amp; item : items) {\r\n    map[getKey(item)] = item;\r\n  }\r\n  return map;\r\n}\r\n\r\nint main()\r\n{\r\n  vector&lt;string&gt; v = {\"a\", \"bb\", \"ccc\"};\r\n  auto map = to_map(v, [](auto const&amp; s) { return s.size(); }); \/\/ works again!\r\n\r\n  cout&lt;&lt; map[3] &lt;&lt; endl; \/\/ prints 'ccc'\r\n  return 0;\r\n}<\/pre>\n<p>Note that now we lose all type safety inside the template and any errors related to calling <code>getKey<\/code> will arise at the time of instantiation. We also lose easy access to the result type and the argument types. The result type still can be retrieved using the <code>decltype<\/code> trick, but we&#8217;re out of luck with argument types.<\/p>\n<p>It is a little said that C++ does not have a good representation of a functional object that works in all contexts, but it&#8217;s hardly surprising given the history of the language.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>TL;DR std::function works great when we know exact types of the arguments and the return value. It breaks down miserably in presence of templates, and the error messages are not <a href=\"https:\/\/ikriv.com\/blog\/?p=4865\" class=\"more-link\">[&hellip;]<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"Layout":"","footnotes":""},"categories":[1],"tags":[],"class_list":["entry","author-ikriv","post-4865","post","type-post","status-publish","format-standard","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/4865","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4865"}],"version-history":[{"count":1,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/4865\/revisions"}],"predecessor-version":[{"id":4866,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/4865\/revisions\/4866"}],"wp:attachment":[{"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4865"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4865"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4865"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}