{"id":102518,"date":"2019-05-27T07:00:00","date_gmt":"2019-05-27T14:00:00","guid":{"rendered":"http:\/\/devblogs.microsoft.com\/oldnewthing\/?p=102518"},"modified":"2019-05-26T22:47:54","modified_gmt":"2019-05-27T05:47:54","slug":"20190527-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190527-00\/?p=102518","title":{"rendered":"Programming puzzle: Creating a map of command handlers given only the function pointer"},"content":{"rendered":"<p>Suppose you have some sort of communication protocol that sends packets of binary-encoded information. There is some information at the start of the packet that describe what command is being sent, and the rest of the bytes in the packet describes the parameters to the command.<\/p>\n<p>The puzzle is to come up with a generic dispatcher that accepts command \/ handler pairs and does the work of extracting the command parameters from the packet and calling the handler.<\/p>\n<p>You are given this class:<\/p>\n<pre>class Packet\r\n{\r\npublic:\r\n int32_t ReadInt32();\r\n uint32_t ReadUInt32();\r\n int8_t ReadInt8();\r\n std::string ReadString();\r\n ... and so on ...\r\n};\r\n<\/pre>\n<p>The <code>Read<\/code> methods parse the next bytes in the packet and produces a corresponding object. Sometimes the object is simple, like an integer. Sometimes it&#8217;s complicated, like a string. Don&#8217;t worry about the details of the parsing; the <code>Packet<\/code> object will do it.<\/p>\n<p>The puzzle is to implement the <code>Dispatcher<\/code> class:<\/p>\n<pre>class Dispatcher\r\n{\r\npublic:\r\n  void AddHandler(uint32_t command, <i>??? SOMETHING ???<\/i>);\r\n  void DispatchCommand(uint32_t command, Packet&amp; packet);\r\n};\r\n<\/pre>\n<p>The intended usage is like this:<\/p>\n<pre>\/\/ Some handler functions\r\nvoid HandleFoo(int32_t, int32_t);\r\nvoid HandleBar(int32_t);\r\nvoid HandleBaz(int32_t, std::string);\r\n\r\n\/\/ Command 0 is the \"Foo\" command that takes\r\n\/\/ two 32-bit integers.\r\ndispatcher.AddHandler(0, HandleFoo);\r\n\r\n\/\/ Command 1 is the \"Bar\" command that takes\r\n\/\/ one 32-bit integer.\r\ndispatcher.AddHandler(1, HandleBar);\r\n\r\n\/\/ Command 4 is the \"Baz\" command that takes\r\n\/\/ a 32-bit integer and a string.\r\ndispatcher.AddHandler(4, HandleBaz);\r\n\r\n\/\/ We received a packet. Dispatch it to a handler.\r\ndispatcher.DispatchCommand(command, packet);\r\n<\/pre>\n<p>The <code>Dispatch\u00adCommand<\/code> method looks up the <code>commandId<\/code> and executes the corresponding handler. In this case, the effect would be as if the <code>Dispatch\u00adCommand<\/code> were written like this:<\/p>\n<pre>void DispatchCommand(uint32_t command, Packet&amp; packet)\r\n{\r\n switch (command) {\r\n case 0:\r\n  {\r\n   auto param1 = packet.ReadInt32();\r\n   auto param2 = packet.ReadInt32();\r\n   HandleFoo(param1, param2);\r\n   break;\r\n  }\r\n case 1:\r\n  {\r\n   auto param1 = packet.ReadInt32();\r\n   HandleBar(param1);\r\n   break;\r\n  }\r\n case 4:\r\n  {\r\n   auto param1 = packet.ReadInt32();\r\n   auto param2 = packet.ReadString();\r\n   HandleFoo(param1, param2);\r\n   break;\r\n  }\r\n\r\n default: std::terminate();\r\n }\r\n}\r\n<\/pre>\n<p>For the purpose of the puzzle, we won&#8217;t worry too much about the case where an invalid command is received. The puzzle is really about the dispatching of valid commands.<\/p>\n<p>Okay, let&#8217;s roll up our sleeves. One way to attack this problem is to do it in a way similar to how we implemented message crackers for Windows messages: Write a custom dispatcher for each function signature.<\/p>\n<pre>class Dispatcher\r\n{\r\n std::map&lt;uint32_t, std::function&lt;void(Packet&amp;)&gt;&gt; commandMap;\r\n\r\npublic:\r\n void AddHandler(uint32_t command, void (*func)(int32_t, int32_t))\r\n {\r\n  commandMap.emplace(command, [func](Packet&amp; packet) {\r\n   auto param1 = packet.ReadInt32();\r\n   auto param2 = packet.ReadInt32();\r\n   func(param1, param2);\r\n  });\r\n }\r\n\r\n void AddHandler(uint32_t command, void (*func)(int32_t))\r\n {\r\n  commandMap.emplace(command, [func](Packet&amp; packet) {\r\n   auto param1 = packet.ReadInt32();\r\n   func(param1);\r\n  });\r\n }\r\n\r\n void AddHandler(uint32_t command, void (*func)(int32_t, std::string))\r\n {\r\n  commandMap.emplace(command, [func](Packet&amp; packet) {\r\n   auto param1 = packet.ReadInt32();\r\n   auto param2 = packet.ReadString();\r\n   func(param1, param2);\r\n  });\r\n }\r\n\r\n ... and so on ...\r\n\r\n void DispatchCommand(uint32_t command, Packet&amp; packet)\r\n {\r\n  auto it = commandMap.find(command);\r\n  if (it == commandMap.end()) std::terminate();\r\n  it-&gt;second(packet);\r\n }\r\n};\r\n<\/pre>\n<p>We write a version of <code>Add\u00adHandler<\/code> for each function signature we care about, and adding a handler consists of creating a lambda which which extracts the relevant parameters from the packet and then calls the handler. These lambdas are captured into a <code>std::function<\/code> and saved in the map for future lookup.<\/p>\n<p>The problem with this technique is that it&#8217;s tedious writing all the lambdas, and the <code>Dispatcher<\/code> class needs to know up front all of the possible function signatures, so it can had an appropriate <code>Add\u00adHandler<\/code> overload. What would be better is if the compiler could write the lambdas automatically based on the parameters to the function. This avoids having to write out all the lambdas, and it means that the <code>Dispatcher<\/code> can handle arbitrary function signatures, not just the ones that were hard-coded into it.<\/p>\n<p>First, we write some helper functions so we can invoke the <code>Read<\/code> methods more template-y-like.<\/p>\n<pre>template&lt;typename T&gt; T Read(Packet&amp; packet) = delete;\r\n\r\ntemplate&lt;&gt; int32_t Read&lt;int32_t&gt;(Packet&amp; packet)\r\n    { return packet.ReadInt32(); }\r\n\r\ntemplate&lt;&gt; uint32_t Read&lt;uint32_t&gt;(Packet&amp; packet)\r\n    { return packet.ReadUInt32(); }\r\n\r\ntemplate&lt;&gt; int8_t Read&lt;int8_t&gt;(Packet&amp; packet)\r\n    { return packet.ReadInt8(); }\r\n\r\ntemplate&lt;&gt; std::string Read&lt;std::string&gt;(Packet&amp; packet)\r\n    { return packet.ReadString(); }\r\n\r\n... and so on ...\r\n<\/pre>\n<p>If somebody needs to read a different kind of thing from a packet, they can add their own specialization of the <code>Read<\/code> function template. They don&#8217;t need to come back to you to ask you to change your <code>Dispatcher<\/code> class.<\/p>\n<p>Now the hard part: Autogenerating the lambdas.<\/p>\n<p>We want a local variable for each parameter. The template parameter pack syntax doesn&#8217;t let us create a variable number of variables, but we can fake it by putting all the variables into a tuple.<\/p>\n<pre>template &lt;typename... Args&gt;\r\nvoid AddHandler(uint32_t command, void(*func)(Args...))\r\n{\r\n  commandMap.emplace(command, [func](Packet&amp; packet) {\r\n    auto args = std::make_tuple(Read&lt;Args&gt;(packet)...);\r\n    std::apply(func, args);\r\n  };\r\n}\r\n<\/pre>\n<p>The idea here is that we create a tuple, each of whose components is the next parameter read from the packet. The templatized <code>Read<\/code> method extracts the parameter from the packet. We take all those parameters, bundle them up into a tuple, and then <code>std::apply<\/code> the function to the tuple, which calls the function with the tuple as arguments.<\/p>\n<p>Unfortunately, this doesn&#8217;t work because it relies on left-to-right order of evaluation of parameters, which C++ does not guarantee. (And in practice, it often isn&#8217;t.)<\/p>\n<p>We need to build up the tuple one component at a time.<\/p>\n<pre>template&lt;typename First, typename... Rest&gt;\r\nstd::tuple&lt;First, Rest...&gt;\r\nread_tuple(Packet&amp; packet)\r\n{\r\n  auto first = std::make_tuple(Read&lt;First&gt;(packet));\r\n  return std::tuple_cat(first, read_tuple&lt;Rest&gt;(packet));\r\n}\r\n\r\nstd::tuple&lt;&gt; read_tuple(Packet&amp; packet)\r\n{\r\n  return std::tuple&lt;&gt;();\r\n}\r\n\r\ntemplate &lt;typename... Args&gt;\r\nvoid AddHandler(uint32_t command, void(*func)(Args...))\r\n{\r\n  commandMap.emplace(command, [func](Packet&amp; packet) {\r\n    auto args = read_tuple(packet);\r\n    std::apply(func, args);\r\n  };\r\n}\r\n<\/pre>\n<p>We use the standard template metaprogramming technique of employing recursion to process each template parameter one at a time. You must resist the temptation to simplify<\/p>\n<pre>  auto first = std::make_tuple(Read&lt;First&gt;(packet));\r\n  return std::tuple_cat(first, read_tuple&lt;Rest&gt;(packet));\r\n<\/pre>\n<p>to<\/p>\n<pre>  return std::tuple_cat(std::make_tuple(Read&lt;First&gt;(packet)),\r\n                        read_tuple&lt;Rest&gt;(packet));\r\n<\/pre>\n<p>because that reintroduces the order-of-evaluation problem the <code>read_<\/code><code>tuple<\/code> function was intended to solve!<\/p>\n<p>The attempted solution doesn&#8217;t compile because you can&#8217;t do this sort of recursive template stuff with functions. (I&#8217;m not sure why.) So we&#8217;ll have to wrap it inside a templatized helper class.<\/p>\n<pre>template&lt;typename... Args&gt;\r\nstruct tuple_reader;\r\n\r\ntemplate&lt;&gt;\r\nstruct tuple_reader&lt;&gt;\r\n{\r\n  static std::tuple&lt;&gt; read(Packet&amp;) { return {}; }\r\n};\r\n\r\ntemplate&lt;typename First, typename... Rest&gt;\r\nstruct tuple_reader&lt;First, Rest...&gt;\r\n{\r\n  static std::tuple&lt;First, Rest...&gt; read(Packet&amp; packet)\r\n  {\r\n    auto first = std::make_tuple(Read&lt;First&gt;(packet));\r\n    return std::tuple_cat(first,\r\n                 tuple_reader&lt;Rest...&gt;::read(packet));\r\n  }\r\n};\r\n\r\ntemplate &lt;typename... Args&gt;\r\nvoid AddHandler(uint32_t command, void(*func)(Args...))\r\n{\r\n  commandMap.emplace(command, [func](Packet&amp; packet) {\r\n    auto args = tuple_reader&lt;Args...&gt;::read(packet);\r\n    std::apply(func, args);\r\n  };\r\n}\r\n<\/pre>\n<p>We start by defining our <code>tuple_<\/code><code>reader<\/code> helper template class as one with a variable number of template parameters.<\/p>\n<p>Next comes the base case: There are no parameters at all. In that case, we return an empty tuple.<\/p>\n<p>Otherwise, we have the recursive case: We peel off the first template parameter and use it to <code>Read<\/code> the corresponding actual parameter from the packet. Then we recursively call ourselves to read the remaining parameters from the packet. And finally, we combine our actual parameter with the tuple produced by the remaining parameters, resulting in the complete tuple.<\/p>\n<p>The <code>std::<\/code><code>tuple_<\/code><code>cat<\/code> function requires tuples, so we take our first parameter and put it in a one-element tuple, so that we can concatenate the second tuple to it.<\/p>\n<p>Now I&#8217;m going to pull a sneaky trick and combine the forward declaration with the recursion base case:<\/p>\n<pre><span style=\"color: red;\">\/\/ Delete \r\n\/\/\r\n\/\/ <span style=\"text-decoration: line-through;\">template&lt;typename... Args&gt;<\/span>\r\n\/\/ <span style=\"text-decoration: line-through;\">struct tuple_reader;<\/span>\r\n\/\/\r\n\/\/ <span style=\"text-decoration: line-through;\">template&lt;&gt;<\/span>\r\n\/\/ <span style=\"text-decoration: line-through;\">struct tuple_reader&lt;&gt;<\/span>\r\n\/\/ <span style=\"text-decoration: line-through;\">{<\/span>\r\n\/\/   <span style=\"text-decoration: line-through;\">static std::tuple&lt;&gt; read(Packet&amp;) { return {}; }<\/span>\r\n\/\/ <span style=\"text-decoration: line-through;\">};<\/span><\/span>\r\n\r\ntemplate&lt;typename... Args&gt;\r\nstruct tuple_reader\r\n{\r\n  static std::tuple&lt;&gt; read(Packet&amp;) { return {}; }\r\n};\r\n<\/pre>\n<p>This trick works because the only thing that will match the template instantiation is the zero-parameter case. If there is one or more parameter, then the <code>First, Rest...<\/code> version will be the one chosen by the compiler.<\/p>\n<p>We&#8217;re almost there. If one of the parameters is non-copyable, the above solution won&#8217;t work because the <code>first<\/code> is passed by copy to <code>std::tuple_<\/code><code>cat<\/code>, and the <code>args<\/code> is passed by copy to <code>std::apply<\/code>.<\/p>\n<p>Even if the parameters are all copyable, the <code>std::move<\/code> is helpful because it avoids unnecessary copies. For example, if a very large string was passed in the packet, we don&#8217;t want to make a copy of the large string just so we can pass it to the handler function. We just want to let the handler function use the string we already read.<\/p>\n<p>To fix that, we do some judicious <code>std::move<\/code>ing.<\/p>\n<pre>template&lt;typename... Args&gt;\r\nstruct tuple_reader\r\n{\r\n  static std::tuple&lt;&gt; read(Packet&amp;) { return {}; }\r\n};\r\n\r\ntemplate&lt;typename First, typename... Rest&gt;\r\nstruct tuple_reader&lt;First, Rest...&gt;\r\n{\r\n  static std::tuple&lt;First, Rest...&gt; read(Packet&amp; packet)\r\n  {\r\n    auto first = std::make_tuple(Read&lt;First&gt;(packet));\r\n    return std::tuple_cat(<span style=\"color: blue;\">std::move(first)<\/span>, \/\/ moved\r\n                 tuple_reader&lt;Rest...&gt;::read(packet));\r\n  }\r\n};\r\n\r\ntemplate &lt;typename... Args&gt;\r\nvoid AddHandler(uint32_t command, void(*func)(Args...))\r\n{\r\n  commandMap.emplace(command, [func](Packet&amp; packet) {\r\n    auto args = tuple_reader&lt;Args...&gt;::read(packet);\r\n    std::apply(func, <span style=\"color: blue;\">std::move(args)<\/span>); \/\/ moved\r\n  };\r\n}\r\n<\/pre>\n<p>The <code>Add\u00adHandler<\/code> method could be condensed slightly, which also saves us the trouble of having to <code>std::move<\/code> the tuple explicitly.<\/p>\n<pre>    std::apply(func, tuple_reader&lt;Args...&gt;::read(packet));\r\n<\/pre>\n<p><b>Exercise 1<\/b>: Change the <code>tuple_<\/code><code>reader<\/code> so it evaluates the template parameters from right to left.<\/p>\n<p><b>Exercise 2<\/b>: Suppose the <code>Packet<\/code> has methods for sending a response to the caller. In that case, the handler should receive a <code>Packet&amp;<\/code> as its first parameter, before the other optional parameters. Extend the above solution to support that.<\/p>\n<p><b>Exercise 3<\/b>: (Harder.) Extend the above solution to support passing an arbitrary function object as a handler, such as a lambda or <code>std::function<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Creating the magic decoder ring automatically.<\/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-102518","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Creating the magic decoder ring automatically.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/102518","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=102518"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/102518\/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=102518"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=102518"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=102518"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}