Have jsonfindptrs use C++17's std::variant

On a mid-range x86_64 laptop, processing the 181 MiB citylots.json file
from github.com/zemirco/sf-city-lots-json:

$ time gen/bin/example-jsonfindptrs < citylots.json > /dev/null
Before this commit:
real    0m7.333s
After:
real    0m5.970s
Ratio: 1.23x

Using "/usr/bin/time -v" instead of "time"
Before this commit:
Maximum resident set size (kbytes): 2556992
After:
Maximum resident set size (kbytes): 1214812
Ratio: 2.10x
diff --git a/build-example.sh b/build-example.sh
index 93d9753..c1d9c82 100755
--- a/build-example.sh
+++ b/build-example.sh
@@ -55,9 +55,11 @@
     fi
   elif [ -e example/$f/*.c ]; then
     echo "Building gen/bin/example-$f"
-    $CC  -O3 example/$f/*.c  -o gen/bin/example-$f
+    $CC  -O3            example/$f/*.c  -o gen/bin/example-$f
+  elif [ $f = "jsonfindptrs" ]; then
+    $CXX -O3 -std=c++17 example/$f/*.cc -o gen/bin/example-$f
   elif [ -e example/$f/*.cc ]; then
     echo "Building gen/bin/example-$f"
-    $CXX -O3 example/$f/*.cc -o gen/bin/example-$f
+    $CXX -O3            example/$f/*.cc -o gen/bin/example-$f
   fi
 done
diff --git a/example/jsonfindptrs/jsonfindptrs.cc b/example/jsonfindptrs/jsonfindptrs.cc
index 683af44..94dc95c 100644
--- a/example/jsonfindptrs/jsonfindptrs.cc
+++ b/example/jsonfindptrs/jsonfindptrs.cc
@@ -56,8 +56,8 @@
 for a C++ compiler $CXX, such as clang++ or g++.
 */
 
-#if defined(__cplusplus) && (__cplusplus < 201103L)
-#error "This C++ program requires -std=c++11 or later"
+#if defined(__cplusplus) && (__cplusplus < 201703L)
+#error "This C++ program requires -std=c++17 or later"
 #endif
 
 #include <stdio.h>
@@ -67,6 +67,9 @@
 #include <string>
 #include <vector>
 
+// <variant> requires C++17.
+#include <variant>
+
 // Wuffs ships as a "single file C library" or "header file library" as per
 // https://github.com/nothings/stb/blob/master/docs/stb_howto.txt
 //
@@ -279,36 +282,34 @@
 
 // ----
 
-class JsonThing {
- public:
-  using Vector = std::vector<JsonThing>;
+struct JsonValue;
 
-  // We use a std::map in this example program to avoid dependencies outside of
-  // the C++ standard library. If you're copy/pasting this JsonThing code,
-  // consider a more efficient data structure such as an absl::btree_map.
-  //
-  // See CppCon 2014: Chandler Carruth "Efficiency with Algorithms, Performance
-  // with Data Structures" at https://www.youtube.com/watch?v=fHNmRkzxHWs
-  using Map = std::map<std::string, JsonThing>;
+using JsonVector = std::vector<JsonValue>;
 
-  enum class Kind {
-    Null,
-    Bool,
-    Int64,
-    Float64,
-    String,
-    Array,
-    Object,
-  } kind = Kind::Null;
+// We use a std::map in this example program to avoid dependencies outside of
+// the C++ standard library. If you're copy/pasting this JsonValue code,
+// consider a more efficient data structure such as an absl::btree_map.
+//
+// See CppCon 2014: Chandler Carruth "Efficiency with Algorithms, Performance
+// with Data Structures" at https://www.youtube.com/watch?v=fHNmRkzxHWs
+using JsonMap = std::map<std::string, JsonValue>;
 
-  struct Value {
-    bool b = false;
-    int64_t i = 0;
-    double f = 0;
-    std::string s;
-    Vector a;
-    Map o;
-  } value;
+using JsonVariant = std::variant<std::monostate,
+                                 bool,
+                                 int64_t,
+                                 double,
+                                 std::string,
+                                 JsonVector,
+                                 JsonMap>;
+
+struct JsonValue : JsonVariant {
+  JsonValue() : JsonVariant() {}
+  JsonValue(bool x) : JsonVariant(x) {}
+  JsonValue(int64_t x) : JsonVariant(x) {}
+  JsonValue(double x) : JsonVariant(x) {}
+  JsonValue(std::string&& x) : JsonVariant(x) {}
+  JsonValue(JsonVector* ignored) : JsonVariant(JsonVector()) {}
+  JsonValue(JsonMap* ignored) : JsonVariant(JsonMap()) {}
 };
 
 // ----
@@ -356,45 +357,41 @@
 }
 
 std::string  //
-print_json_pointers(JsonThing& jt, uint32_t depth) {
+print_json_pointers(JsonValue& jvalue, uint32_t depth) {
   std::cout << g_dst << '\n';
   if (depth++ >= g_flags.max_output_depth) {
     return "";
   }
 
   size_t n = g_dst.size();
-  switch (jt.kind) {
-    case JsonThing::Kind::Array:
-      g_dst += "/";
-      for (size_t i = 0; i < jt.value.a.size(); i++) {
-        if (i >= g_to_string_cache.size()) {
-          g_to_string_cache.push_back(std::to_string(i));
-        }
-        g_dst += g_to_string_cache[i];
-        TRY(print_json_pointers(jt.value.a[i], depth));
-        g_dst.resize(n + 1);
+  if (std::holds_alternative<JsonVector>(jvalue)) {
+    JsonVector& jvector = std::get<JsonVector>(jvalue);
+    g_dst += "/";
+    for (size_t i = 0; i < jvector.size(); i++) {
+      if (i >= g_to_string_cache.size()) {
+        g_to_string_cache.push_back(std::to_string(i));
       }
-      g_dst.resize(n);
-      break;
-    case JsonThing::Kind::Object:
-      g_dst += "/";
-      for (auto& kv : jt.value.o) {
-        if (!escape_needed(kv.first)) {
-          g_dst += kv.first;
-        } else {
-          std::string e = escape(kv.first);
-          if (e.empty()) {
-            return "main: unsupported \"\\u000A\" or \"\\u000D\" in object key";
-          }
-          g_dst += e;
+      g_dst += g_to_string_cache[i];
+      TRY(print_json_pointers(jvector[i], depth));
+      g_dst.resize(n + 1);
+    }
+    g_dst.resize(n);
+  } else if (std::holds_alternative<JsonMap>(jvalue)) {
+    g_dst += "/";
+    for (auto& kv : std::get<JsonMap>(jvalue)) {
+      if (!escape_needed(kv.first)) {
+        g_dst += kv.first;
+      } else {
+        std::string e = escape(kv.first);
+        if (e.empty()) {
+          return "main: unsupported \"\\u000A\" or \"\\u000D\" in object key";
         }
-        TRY(print_json_pointers(kv.second, depth));
-        g_dst.resize(n + 1);
+        g_dst += e;
       }
-      g_dst.resize(n);
-      break;
-    default:
-      break;
+      TRY(print_json_pointers(kv.second, depth));
+      g_dst.resize(n + 1);
+    }
+    g_dst.resize(n);
   }
   return "";
 }
@@ -404,92 +401,65 @@
 class Callbacks : public wuffs_aux::DecodeJsonCallbacks {
  public:
   struct Entry {
-    Entry(JsonThing&& jt)
-        : thing(std::move(jt)), has_map_key(false), map_key() {}
+    Entry(JsonValue&& jvalue_arg)
+        : jvalue(std::move(jvalue_arg)), has_map_key(false), map_key() {}
 
-    JsonThing thing;
+    JsonValue jvalue;
     bool has_map_key;
     std::string map_key;
   };
 
   Callbacks() = default;
 
-  std::string Append(JsonThing&& jt) {
+  std::string Append(JsonValue&& jvalue) {
     if (m_stack.empty()) {
-      m_stack.push_back(Entry(std::move(jt)));
+      m_stack.push_back(Entry(std::move(jvalue)));
       return "";
     }
     Entry& top = m_stack.back();
-    switch (top.thing.kind) {
-      case JsonThing::Kind::Array:
-        top.thing.value.a.push_back(std::move(jt));
-        return "";
-      case JsonThing::Kind::Object:
-        if (top.has_map_key) {
-          top.has_map_key = false;
-          auto iter = top.thing.value.o.find(top.map_key);
-          if (iter != top.thing.value.o.end()) {
-            return "main: duplicate key: " + top.map_key;
-          }
-          top.thing.value.o.insert(
-              iter, JsonThing::Map::value_type(std::move(top.map_key),
-                                               std::move(jt)));
-          return "";
-        } else if (jt.kind == JsonThing::Kind::String) {
-          top.has_map_key = true;
-          top.map_key = std::move(jt.value.s);
-          return "";
+    if (std::holds_alternative<JsonVector>(top.jvalue)) {
+      std::get<JsonVector>(top.jvalue).push_back(std::move(jvalue));
+      return "";
+    } else if (std::holds_alternative<JsonMap>(top.jvalue)) {
+      JsonMap& jmap = std::get<JsonMap>(top.jvalue);
+      if (top.has_map_key) {
+        top.has_map_key = false;
+        auto iter = jmap.find(top.map_key);
+        if (iter != jmap.end()) {
+          return "main: duplicate key: " + top.map_key;
         }
-        return "main: internal error: non-string map key";
-      default:
-        return "main: internal error: non-container stack entry";
+        jmap.insert(iter, JsonMap::value_type(std::move(top.map_key),
+                                              std::move(jvalue)));
+        return "";
+      } else if (std::holds_alternative<std::string>(jvalue)) {
+        top.has_map_key = true;
+        top.map_key = std::move(std::get<std::string>(jvalue));
+        return "";
+      }
+      return "main: internal error: non-string map key";
+    } else {
+      return "main: internal error: non-container stack entry";
     }
   }
 
-  std::string AppendNull() override {
-    JsonThing jt;
-    jt.kind = JsonThing::Kind::Null;
-    return Append(std::move(jt));
-  }
+  std::string AppendNull() override { return Append(JsonValue()); }
 
-  std::string AppendBool(bool val) override {
-    JsonThing jt;
-    jt.kind = JsonThing::Kind::Bool;
-    jt.value.b = val;
-    return Append(std::move(jt));
-  }
+  std::string AppendBool(bool val) override { return Append(JsonValue(val)); }
 
-  std::string AppendI64(int64_t val) override {
-    JsonThing jt;
-    jt.kind = JsonThing::Kind::Int64;
-    jt.value.i = val;
-    return Append(std::move(jt));
-  }
+  std::string AppendI64(int64_t val) override { return Append(JsonValue(val)); }
 
-  std::string AppendF64(double val) override {
-    JsonThing jt;
-    jt.kind = JsonThing::Kind::Float64;
-    jt.value.f = val;
-    return Append(std::move(jt));
-  }
+  std::string AppendF64(double val) override { return Append(JsonValue(val)); }
 
   std::string AppendTextString(std::string&& val) override {
-    JsonThing jt;
-    jt.kind = JsonThing::Kind::String;
-    jt.value.s = std::move(val);
-    return Append(std::move(jt));
+    return Append(JsonValue(std::move(val)));
   }
 
   std::string Push(uint32_t flags) override {
     if (flags & WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_LIST) {
-      JsonThing jt;
-      jt.kind = JsonThing::Kind::Array;
-      m_stack.push_back(std::move(jt));
+      m_stack.push_back(JsonValue(static_cast<JsonVector*>(nullptr)));
       return "";
     } else if (flags & WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_DICT) {
-      JsonThing jt;
-      jt.kind = JsonThing::Kind::Object;
-      m_stack.push_back(std::move(jt));
+      m_stack.push_back(JsonValue(static_cast<JsonMap*>(nullptr)));
       return "";
     }
     return "main: internal error: bad push";
@@ -499,9 +469,9 @@
     if (m_stack.empty()) {
       return "main: internal error: bad pop";
     }
-    JsonThing jt = std::move(m_stack.back().thing);
+    JsonValue jvalue = std::move(m_stack.back().jvalue);
     m_stack.pop_back();
-    return Append(std::move(jt));
+    return Append(std::move(jvalue));
   }
 
   void Done(wuffs_aux::DecodeJsonResult& result,
@@ -513,7 +483,7 @@
       result.error_message = "main: internal error: bad depth";
       return;
     }
-    result.error_message = print_json_pointers(m_stack.back().thing, 0);
+    result.error_message = print_json_pointers(m_stack.back().jvalue, 0);
   }
 
  private: