{"id":103755,"date":"2020-05-15T07:00:00","date_gmt":"2020-05-15T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=103755"},"modified":"2020-05-15T06:11:47","modified_gmt":"2020-05-15T13:11:47","slug":"20200515-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200515-00\/?p=103755","title":{"rendered":"Storing a non-capturing lambda in a generic object"},"content":{"rendered":"<p>Suppose you want an object that can store a non-capturing lambda. You don&#8217;t want to use <code>std::function<\/code> because it&#8217;s too heavy, for some sense of &#8220;heavy&#8221;. Perhaps you don&#8217;t like the fact that its copy constructor can throw. Or that it sometimes requires two allocations. Or that there&#8217;s a virtual function call.<\/p>\n<p>Fortunately, non-capturing lambdas are convertible to function pointers, so really you are just storing function pointers. Let&#8217;s say that the lambda is going to be called with a single integer parameter.<\/p>\n<pre>struct event_source\r\n{\r\n  using handler_t = bool (*)(int);\r\n\r\n  handler_t m_handler = nullptr;\r\n\r\n  void set_handler(handler_t handler)\r\n  {\r\n    m_handler = handler;\r\n  }\r\n\r\n  void raise(int value)\r\n  {\r\n    if (m_handler) m_handler(value);\r\n  }\r\n};\r\n<\/pre>\n<p>Registering a handler with a captureless lambda is straightforward thanks to the implicit conversion to a function pointer:<\/p>\n<pre>event_source e;\r\n\r\nvoid f()\r\n{\r\n  e.set_handler([](int value) { log(value); return true; });\r\n}\r\n\r\nvoid g()\r\n{\r\n  e.raise(42);\r\n}\r\n<\/pre>\n<p>A plain function pointer isn&#8217;t that great because it doesn&#8217;t have any context. Let&#8217;s provide a context parameter so the function can remember its environment.<\/p>\n<pre>struct event_source\r\n{\r\n  using handler_t = bool (*)(int, void*);\r\n\r\n  <span style=\"color: blue;\">const void* m_context;<\/span>\r\n  handler_t m_handler = nullptr;\r\n\r\n  void set_handler(handler_t handler<span style=\"color: blue;\">, const void* context<\/span>)\r\n  {\r\n    m_handler = handler;\r\n    <span style=\"color: blue;\">m_context = context;<\/span>\r\n  }\r\n\r\n  void raise(int value)\r\n  {\r\n    if (m_handler) {\r\n      m_handler(value<span style=\"color: blue;\">, const_cast&lt;void*&gt;(m_context)<\/span>);\r\n    }\r\n  }\r\n};\r\n<\/pre>\n<p>So far so good, but one frustration is that the handler has to cast the <code>context<\/code> parameter back to whatever it originally was:<\/p>\n<pre>void f(widget* widget)\r\n{\r\n  e.set_handler([](int value, void* context)\r\n    {\r\n      <span style=\"color: blue;\">auto widget = reinterpret_cast&lt;widget*&gt;(context);<\/span>\r\n      ... use the widget and value ...\r\n      return true;\r\n    }, widget);\r\n}\r\n<\/pre>\n<p>Wouldn&#8217;t it be nice if we could automatically pass the context back through in the same form it was received?<\/p>\n<pre>struct event_source\r\n{\r\n  template&lt;typename T = void&gt;\r\n  using handler_t = bool (*)(int, T*);\r\n\r\n  const void* m_context;\r\n  <span style=\"color: blue;\">handler_t&lt;&gt;<\/span> m_handler = nullptr;\r\n  <span style=\"color: blue;\">bool (*m_adapter)(handler_t&lt;&gt;, int, const void*);<\/span>\r\n\r\n  <span style=\"color: blue;\">template&lt;typename T&gt;<\/span>\r\n  void set_handler(<span style=\"color: blue;\">handler_t&lt;T&gt; handler, T* context<\/span>)\r\n  {\r\n    m_handler = <span style=\"color: blue;\">reinterpret_cast&lt;handler_t&amp;lt&gt;&gt;<\/span>(handler);\r\n    m_context = context;\r\n    <span style=\"color: blue;\">m_adapter = [](handler_t&amp;lt&gt; raw_handler,\r\n                   int value, const void* raw_context)\r\n    {\r\n      auto handler = reinterpret_cast&lt;handler_t&lt;T&gt;&gt;(raw_handler);\r\n      auto context = reinterpret_cast&lt;T*&gt;(raw_context);\r\n      return handler(value, context);\r\n    };<\/span>\r\n  }\r\n\r\n  void raise(int value)\r\n  {\r\n    if (m_handler) {\r\n      <span style=\"color: blue;\">m_adapter(m_handler, value, m_context);<\/span>\r\n    }\r\n  }\r\n};\r\n<\/pre>\n<p>We are basically mimicking what <code>std::function<\/code> does, but taking advantage of optimizations that are available because of our special restrictions.<\/p>\n<p>First of all, we know that the handler is just a function pointer, so we don&#8217;t need variable-sized storage. As we noted earlier, C++ requires that a function pointer can be cast to any other kind of function pointer, and then recovered by casting back. So we&#8217;ll just use <code>handler_t&lt;&gt;<\/code> as our generic storage.<\/p>\n<p>Second, function pointers are scalar, hence trivial, so they can be copied and moved by <code>memmove<\/code> and require no special construction or destruction. This means that the only remaining virtual method of our <code>callable_base<\/code> is <code>invoke<\/code>.<\/p>\n<p>Since there is only one virtual method, we can dispense with the vtable and just record the function pointer directly. This avoids a level of indirection.<\/p>\n<p>The function pointer we record is the &#8220;adapter&#8221; which takes the raw storage and raw context, casts them back to the original function pointer type and context type, and then calls the original function pointer with the original context.<\/p>\n<p>On modern architectures, the &#8220;adapter&#8221; function is effectively a nop because all pointers are ABI-compatible, but the C++ language is not as permissive as the ABI, so we need the adapter to keep everything honest. In practice, every type will have the same adapter, and the adapter itself will be trivial.<\/p>\n<p>If you know that the calling convention for your ABI is register-based, you can do some micro-optimizing by moving the <code>handler<\/code> parameter to the end of the parameter list. That makes the adapter a single <i>jump to register<\/i> instruction.<\/p>\n<p>Let&#8217;s take this out for a spin:<\/p>\n<pre>void f()\r\n{\r\n  e.set_handler([](int value, const char* context)\r\n    { log(context, value); return true; }, \"hello\");\r\n}\r\n<\/pre>\n<p>This fails to compile:<\/p>\n<pre style=\"white-space: pre-wrap;\">error: no matching member function for call to 'set_handler'\r\nnote: candidate template ignored: could not match 'handler_t&lt;T&gt;' against '(lambda)'\r\n<\/pre>\n<p>Although there is a conversion from the captureless lambda to <code>bool (*)(int, const char*)<\/code>, the conversion is not considered by the template matching machinery.<\/p>\n<p>We&#8217;ll have to help it along.<\/p>\n<pre>  template&lt;<span style=\"color: blue;\">typename TLambda<\/span>, typename T&gt;\r\n  void set_handler(<span style=\"color: blue;\">TLambda&amp;&amp; handler<\/span>, T* context)\r\n  {\r\n    m_handler = reinterpret_cast&lt;handler&lt;&gt;&gt;(\r\n                  <span style=\"color: blue;\">static_cast&lt;handler_t&lt;T&gt;&gt;<\/span>(handler));\r\n    m_context = context;\r\n    m_adapter = [](handler_t&amp;lt&gt; raw_handler,\r\n                   int value, const void* raw_context)\r\n    {\r\n      auto handler = reinterpret_cast&lt;handler_t&lt;T&gt;&gt;(raw_handler);\r\n      auto context = reinterpret_cast&lt;T*&gt;(raw_context);\r\n      return handler(value, context);\r\n    };\r\n  }\r\n<\/pre>\n<p>Our <code>set_handler<\/code> accepts anything as its first parameter, but then immediately <code>static_cast<\/code>s it to a function accepting a matching context.<\/p>\n<p>This design makes the final parameter (the <code>context<\/code>) the final arbiter of the signature of the lambda. This is troublesome if the context is <code>nullptr<\/code>, because the type of <code>nullptr<\/code> is <code>nullptr_t<\/code>, which is not a pointer to anything. You&#8217;ll have to cast the <code>nullptr_t<\/code> explicitly to the desired context type.<\/p>\n<pre>void f()\r\n{\r\n  e.set_handler([](int value, const char* context)\r\n    { log(context, value); return true; },\r\n    <span style=\"color: blue;\">(const char*)nullptr<\/span>);\r\n}\r\n<\/pre>\n<p>So there you have it. A &#8220;lighter&#8221; version of <code>std::function<\/code> that accepts only function pointers and things convertible to function pointers (which includes captureless lambdas), plus a context parameter, which is basically a special case of <code>std::bind<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Reimplementing a small corner of <CODE>std::function<\/CODE>.<\/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-103755","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Reimplementing a small corner of <CODE>std::function<\/CODE>.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103755","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=103755"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103755\/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=103755"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=103755"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=103755"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}