{"id":102604,"date":"2019-06-20T07:00:00","date_gmt":"2019-06-20T14:00:00","guid":{"rendered":"http:\/\/devblogs.microsoft.com\/oldnewthing\/?p=102604"},"modified":"2019-06-21T10:59:47","modified_gmt":"2019-06-21T17:59:47","slug":"20190620-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190620-00\/?p=102604","title":{"rendered":"Getting a value from a <CODE>std::variant<\/CODE> that matches the type fetched from another variant"},"content":{"rendered":"<p>Suppose you have two <code>std::variant<\/code> objects of the same type and you want to perform some operation on corresponding pairs of types.<\/p>\n<pre>using my_variant = std::variant&lt;int, double, std::string&gt;;\r\n\r\nbool are_equivalent(my_variant const&amp; left,\r\n                    my_variant const&amp; right)\r\n{\r\n  if (left.index() != right.index()) return false;\r\n\r\n  switch (left.index())\r\n  {\r\n  case 0:\r\n    return are_equivalent(std::get&lt;0&gt;(left),\r\n                          std::get&lt;0&gt;(right));\r\n    break;\r\n\r\n  case 1:\r\n    return are_equivalent(std::get&lt;1&gt;(left),\r\n                          std::get&lt;1&gt;(right));\r\n    break;\r\n\r\n  default:\r\n    return are_equivalent(std::get&lt;2&gt;(left),\r\n                          std::get&lt;2&gt;(right));\r\n    break;\r\n  }\r\n}\r\n<\/pre>\n<p>Okay, what&#8217;s going on here?<\/p>\n<p>We have a <code>std::variant<\/code> that can hold one of three possible types. First, we see if the two variants are even holding the same types. If not, then they are definitely not equivalent.<\/p>\n<p>Otherwise, we check what is in the <code>left<\/code> object by switching on the index, and then check if the corresponding contents are equivalent.<\/p>\n<p>In the case I needed to do this, the variants were part of a recursive data structure, so the recursive call to <code>are_equivalent<\/code> really did recurse deeper into the data structure.<\/p>\n<p>There&#8217;s a little trick hiding in the <code>default<\/code> case: That case gets hit either when the index is 2, indicating that we have a <code>std:string<\/code>, or when the index is <code>variant_<\/code><code>npos<\/code>, indicating that the variant is <a href=\"https:\/\/en.cppreference.com\/w\/cpp\/utility\/variant\/valueless_by_exception\"> in a horrible state<\/a>. If it does indeed hold a string, then the calls to <code>std::get&lt;2&gt;<\/code> succeed, and if it&#8217;s in a horrible state, we get a <a href=\"https:\/\/en.cppreference.com\/w\/cpp\/utility\/variant\/bad_variant_access\"> <code>bad_<\/code><code>variant_<\/code><code>access<\/code> exception<\/a>.<\/p>\n<p>This is tedious code to write. Surely there must be a better way.<\/p>\n<p>What I came up with was to use the visitor pattern with a templated handler.<\/p>\n<pre>bool are_equivalent(my_variant const&amp; left,\r\n                    my_variant const&amp; right)\r\n{\r\n  if (left.index() != right.index()) return false;\r\n\r\n  <span style=\"color: blue;\">return std::visit([&amp;](auto const&amp; l)\r\n    {\r\n      using T = std::decay_t&lt;decltype(l)&gt;;\r\n      return are_equivalent(l, std::get&lt;T&gt;(right));\r\n    }, left);<\/span>\r\n}\r\n<\/pre>\n<p>After verifying that the indices match, we visit the variant with a generic lambda and then reverse-engineer the appropriate getter to use for the right hand side by studying the type of the thing we were given. The <code>std::get&lt;T&gt;<\/code> will not throw because we already validated that the types match. (On the other hand, the entire <code>std::visit<\/code> could throw if both <code>left<\/code> and <code>right<\/code> are in horrible states.)<\/p>\n<p>Note that this trick fails if the variant repeats types, because the type passed to <code>std::get<\/code> is now ambiguous.<\/p>\n<p>Anyway, I had to use this pattern in a few places, so I wrote a helper function:<\/p>\n<pre>template&lt;typename Template, typename... Args&gt;\r\ndecltype(auto)\r\nget_matching_alternative(\r\n    const std::variant&lt;Args...&gt;&amp; v,\r\n    Template&amp;&amp;)\r\n{\r\n    using T = typename std::decay_t&lt;Template&gt;;\r\n    return std::get&lt;T&gt;(v);\r\n}\r\n<\/pre>\n<p>You pass this helper the variant you have and something that represents the thing you want, and the function returns the corresponding thing from the variant. With this helper, the <code>are_<\/code><code>equivalent<\/code> function looks like this:<\/p>\n<pre>bool are_equivalent(my_variant const&amp; left,\r\n                    my_variant const&amp; right)\r\n{\r\n  if (left.index() != right.index()) return false;\r\n\r\n  return std::visit([&amp;](auto const&amp; l)\r\n    {\r\n      return are_equivalent(l,\r\n                   <span style=\"color: blue;\">get_matching_alternative(right, l)<\/span>);\r\n    });\r\n}\r\n<\/pre>\n<p>I&#8217;m still not entirely happy with this, though. Maybe you can come up with something better?<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A bunch of type fiddling, but you&#8217;ll get there eventually.<\/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-102604","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>A bunch of type fiddling, but you&#8217;ll get there eventually.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/102604","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=102604"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/102604\/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=102604"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=102604"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=102604"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}