[C++17] Add compile-time reflection for fields. (#6324)
authorDavid P. Sicilia <dpacbach@users.noreply.github.com>
Fri, 5 Mar 2021 18:01:40 +0000 (13:01 -0500)
committerGitHub <noreply@github.com>
Fri, 5 Mar 2021 18:01:40 +0000 (10:01 -0800)
* [C++17] Add compile-time reflection for fields.

Included in this commit is the following:

  - The C++ generator has been modified so that,
    when in C++17 mode, it will emit Table and
    Struct field traits that can be used at com-
    pile time as a form of static reflection. This
    includes field types, field names, and a tuple
    of field getter results.

  - Diffs to the cpp17 generated files. No other
    generated files are affected.

  - A unit test that also serves as an example. It
    demonstrates how to use the full power of this
    reflection to implement a full recursive
    JSON-like stringifier for Flatbuffers types,
    but without needing any runtime access to the
    *.fbs definition files; the computation is
    done using only static reflection.

Tested on Linux with gcc 10.2.0.

Fixes #6285.

* Fix int-conversion warning on MSVC.

* Try to fix std::to_string ambiguity on MSVC.

* Fix clang-format diffs.

* Fix more clang-format diffs.

* Fix last clang-format diff.

* Enable C++17 build/test for VC 19 platform in CI.

* Forgot to add value to cmake command line variable.

* Various fixes/changes in response to @vglavnyy's feedback.

* Replace "fields pack" with index-based getters.

* Fix MSVC error.

* Fix clang-format diffs.

* getter_for method returns result instead of address-of-getter.

* Next round of reviewer suggestions.

* Use type instead of hardcoded struct name.

* Fix clang-format diff.

* Add test for FieldType since it is not used in the stringify test.

* Add fields_number field to Traits struct.

* Add --cpp-static-reflection flag and put those features behind it.

* Fix clang-format diffs.

* Remove <tuple> include.

.github/workflows/build.yml
include/flatbuffers/idl.h
src/flatc.cpp
src/idl_gen_cpp.cpp
tests/cpp17/generated_cpp17/monster_test_generated.h
tests/cpp17/generated_cpp17/optional_scalars_generated.h
tests/cpp17/stringify_util.h [new file with mode: 0644]
tests/cpp17/test_cpp17.cpp
tests/generate_code.sh

index 7b01c98..12f046e 100644 (file)
@@ -37,7 +37,7 @@ jobs:
     - name: Add msbuild to PATH
       uses: microsoft/setup-msbuild@v1.0.2
     - name: cmake
-      run: cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_BUILD_TYPE=Release .
+      run: cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_BUILD_TYPE=Release -DFLATBUFFERS_BUILD_CPP17=ON .
     - name: build
       run: msbuild.exe FlatBuffers.sln /p:Configuration=Release /p:Platform=x64
     - name: test
index 0e9a3bc..d9ef95b 100644 (file)
@@ -588,6 +588,7 @@ struct IDLOptions {
   bool cs_gen_json_serializer;
   std::vector<std::string> cpp_includes;
   std::string cpp_std;
+  bool cpp_static_reflection;
   std::string proto_namespace_suffix;
   std::string filename_suffix;
   std::string filename_extension;
@@ -673,6 +674,7 @@ struct IDLOptions {
         force_defaults(false),
         java_primitive_has_method(false),
         cs_gen_json_serializer(false),
+        cpp_static_reflection(false),
         filename_suffix("_generated"),
         filename_extension(),
         no_warnings(false),
index 6eeefab..221b886 100644 (file)
@@ -126,6 +126,9 @@ std::string FlatCompiler::GetUsageString(const char *program_name) const {
     "                          * 'c++0x' - generate code compatible with old compilers;\n"
     "                          * 'c++11' - use C++11 code generator (default);\n"
     "                          * 'c++17' - use C++17 features in generated code (experimental).\n"
+    "  --cpp-static-reflection When using C++17, generate extra code to provide compile-time\n"
+    "                          (static) reflection of Flatbuffers types.  Requires --cpp-std\n"
+    "                          to be \"c++17\" or higher.\n"
     "  --object-prefix        Customise class prefix for C++ object-based API.\n"
     "  --object-suffix        Customise class suffix for C++ object-based API.\n"
     "                         Default value is \"T\".\n"
@@ -360,6 +363,8 @@ int FlatCompiler::Compile(int argc, const char **argv) {
         opts.cpp_std = argv[argi];
       } else if (arg.rfind("--cpp-std=", 0) == 0) {
         opts.cpp_std = arg.substr(std::string("--cpp-std=").size());
+      } else if (arg == "--cpp-static-reflection") {
+        opts.cpp_static_reflection = true;
       } else {
         for (size_t i = 0; i < params_.num_generators; ++i) {
           if (arg == params_.generators[i].generator_opt_long ||
index 5ac5f7c..d9f495b 100644 (file)
@@ -2041,6 +2041,135 @@ class CppGenerator : public BaseGenerator {
     if (type.base_type == BASE_TYPE_UNION) { GenTableUnionAsGetters(field); }
   }
 
+  void GenTableFieldType(const FieldDef &field) {
+    const auto &type = field.value.type;
+    const auto offset_str = GenFieldOffsetName(field);
+    if (!field.IsScalarOptional()) {
+      std::string afterptr = " *" + NullableExtension();
+      code_.SetValue("FIELD_TYPE",
+                     GenTypeGet(type, "", "const ", afterptr.c_str(), true));
+      code_ += "    {{FIELD_TYPE}}\\";
+    } else {
+      code_.SetValue("FIELD_TYPE", GenOptionalDecl(type));
+      code_ += "    {{FIELD_TYPE}}\\";
+    }
+  }
+
+  void GenStructFieldType(const FieldDef &field) {
+    const auto is_array = IsArray(field.value.type);
+    std::string field_type =
+        GenTypeGet(field.value.type, "", is_array ? "" : "const ",
+                   is_array ? "" : " &", true);
+    code_.SetValue("FIELD_TYPE", field_type);
+    code_ += "    {{FIELD_TYPE}}\\";
+  }
+
+  void GenFieldTypeHelper(const StructDef &struct_def) {
+    if (struct_def.fields.vec.empty()) { return; }
+    code_ += "  template<size_t Index>";
+    code_ += "  using FieldType = \\";
+    code_ += "decltype(std::declval<type>().get_field<Index>());";
+  }
+
+  void GenIndexBasedFieldGetter(const StructDef &struct_def) {
+    if (struct_def.fields.vec.empty()) { return; }
+    code_ += "  template<size_t Index>";
+    code_ += "  auto get_field() const {";
+
+    size_t index = 0;
+    bool need_else = false;
+    // Generate one index-based getter for each field.
+    for (auto it = struct_def.fields.vec.begin();
+         it != struct_def.fields.vec.end(); ++it) {
+      const auto &field = **it;
+      if (field.deprecated) {
+        // Deprecated fields won't be accessible.
+        continue;
+      }
+      code_.SetValue("FIELD_NAME", Name(field));
+      code_.SetValue("FIELD_INDEX",
+                     std::to_string(static_cast<long long>(index++)));
+      if (need_else) {
+        code_ += "    else \\";
+      } else {
+        code_ += "         \\";
+      }
+      need_else = true;
+      code_ += "if constexpr (Index == {{FIELD_INDEX}}) \\";
+      code_ += "return {{FIELD_NAME}}();";
+    }
+    code_ += "    else static_assert(Index != Index, \"Invalid Field Index\");";
+    code_ += "  }";
+  }
+
+  // Sample for Vec3:
+  //
+  //   static constexpr std::array<const char *, 3> field_names = {
+  //     "x",
+  //     "y",
+  //     "z"
+  //   };
+  //
+  void GenFieldNames(const StructDef &struct_def) {
+    auto non_deprecated_field_count = std::count_if(
+        struct_def.fields.vec.begin(), struct_def.fields.vec.end(),
+        [](const FieldDef *field) { return !field->deprecated; });
+    code_ += "  static constexpr std::array<\\";
+    code_.SetValue(
+        "FIELD_COUNT",
+        std::to_string(static_cast<long long>(non_deprecated_field_count)));
+    code_ += "const char *, {{FIELD_COUNT}}> field_names = {\\";
+    if (struct_def.fields.vec.empty()) {
+      code_ += "};";
+      return;
+    }
+    code_ += "";
+    // Generate the field_names elements.
+    for (auto it = struct_def.fields.vec.begin();
+         it != struct_def.fields.vec.end(); ++it) {
+      const auto &field = **it;
+      if (field.deprecated) {
+        // Deprecated fields won't be accessible.
+        continue;
+      }
+      code_.SetValue("FIELD_NAME", Name(field));
+      code_ += "    \"{{FIELD_NAME}}\"\\";
+      if (it + 1 != struct_def.fields.vec.end()) { code_ += ","; }
+    }
+    code_ += "\n  };";
+  }
+
+  void GenFieldsNumber(const StructDef &struct_def) {
+    auto non_deprecated_field_count = std::count_if(
+        struct_def.fields.vec.begin(), struct_def.fields.vec.end(),
+        [](const FieldDef *field) { return !field->deprecated; });
+    code_.SetValue(
+        "FIELD_COUNT",
+        std::to_string(static_cast<long long>(non_deprecated_field_count)));
+    code_ += "  static constexpr size_t fields_number = {{FIELD_COUNT}};";
+  }
+
+  void GenTraitsStruct(const StructDef &struct_def) {
+    code_.SetValue(
+        "FULLY_QUALIFIED_NAME",
+        struct_def.defined_namespace->GetFullyQualifiedName(Name(struct_def)));
+    code_ += "struct {{STRUCT_NAME}}::Traits {";
+    code_ += "  using type = {{STRUCT_NAME}};";
+    if (!struct_def.fixed) {
+      // We have a table and not a struct.
+      code_ += "  static auto constexpr Create = Create{{STRUCT_NAME}};";
+    }
+    code_ += "  static constexpr auto name = \"{{STRUCT_NAME}}\";";
+    code_ +=
+        "  static constexpr auto fully_qualified_name = "
+        "\"{{FULLY_QUALIFIED_NAME}}\";";
+    GenFieldNames(struct_def);
+    GenFieldTypeHelper(struct_def);
+    GenFieldsNumber(struct_def);
+    code_ += "};";
+    code_ += "";
+  }
+
   void GenTableFieldSetter(const FieldDef &field) {
     const auto &type = field.value.type;
     const bool is_scalar = IsScalar(type.base_type);
@@ -2098,7 +2227,7 @@ class CppGenerator : public BaseGenerator {
       code_ += "  typedef {{NATIVE_NAME}} NativeTableType;";
     }
     code_ += "  typedef {{STRUCT_NAME}}Builder Builder;";
-    if (opts_.g_cpp_std >= cpp::CPP_STD_17) { code_ += "  struct Traits;"; }
+    if (opts_.cpp_static_reflection) { code_ += "  struct Traits;"; }
     if (opts_.mini_reflect != IDLOptions::kNone) {
       code_ +=
           "  static const flatbuffers::TypeTable *MiniReflectTypeTable() {";
@@ -2181,6 +2310,8 @@ class CppGenerator : public BaseGenerator {
       if (field.key) { GenKeyFieldMethods(field); }
     }
 
+    if (opts_.cpp_static_reflection) { GenIndexBasedFieldGetter(struct_def); }
+
     // Generate a verifier function that can check a buffer from an untrusted
     // source will never cause reads outside the buffer.
     code_ += "  bool Verify(flatbuffers::Verifier &verifier) const {";
@@ -2388,13 +2519,7 @@ class CppGenerator : public BaseGenerator {
 
     // Definition for type traits for this table type. This allows querying var-
     // ious compile-time traits of the table.
-    if (opts_.g_cpp_std >= cpp::CPP_STD_17) {
-      code_ += "struct {{STRUCT_NAME}}::Traits {";
-      code_ += "  using type = {{STRUCT_NAME}};";
-      code_ += "  static auto constexpr Create = Create{{STRUCT_NAME}};";
-      code_ += "};";
-      code_ += "";
-    }
+    if (opts_.cpp_static_reflection) { GenTraitsStruct(struct_def); }
 
     // Generate a CreateXDirect function with vector types as parameters
     if (opts_.cpp_direct_copy && has_string_or_vector_fields) {
@@ -3168,6 +3293,8 @@ class CppGenerator : public BaseGenerator {
     code_ += "";
     code_ += " public:";
 
+    if (opts_.cpp_static_reflection) { code_ += "  struct Traits;"; }
+
     // Make TypeTable accessible via the generated struct.
     if (opts_.mini_reflect != IDLOptions::kNone) {
       code_ +=
@@ -3253,12 +3380,19 @@ class CppGenerator : public BaseGenerator {
     }
     code_.SetValue("NATIVE_NAME", Name(struct_def));
     GenOperatorNewDelete(struct_def);
+
+    if (opts_.cpp_static_reflection) { GenIndexBasedFieldGetter(struct_def); }
+
     code_ += "};";
 
     code_.SetValue("STRUCT_BYTE_SIZE", NumToString(struct_def.bytesize));
     code_ += "FLATBUFFERS_STRUCT_END({{STRUCT_NAME}}, {{STRUCT_BYTE_SIZE}});";
     if (opts_.gen_compare) GenCompareOperator(struct_def, "()");
     code_ += "";
+
+    // Definition for type traits for this table type. This allows querying var-
+    // ious compile-time traits of the table.
+    if (opts_.cpp_static_reflection) { GenTraitsStruct(struct_def); }
   }
 
   // Set up the correct namespace. Only open a namespace if the existing one is
@@ -3331,6 +3465,13 @@ bool GenerateCPP(const Parser &parser, const std::string &path,
   // The opts.scoped_enums has priority.
   opts.g_only_fixed_enums |= opts.scoped_enums;
 
+  if (opts.cpp_static_reflection && opts.g_cpp_std < cpp::CPP_STD_17) {
+    LogCompilerError(
+        "--cpp-static-reflection requires using --cpp-std at \"C++17\" or "
+        "higher.");
+    return false;
+  }
+
   cpp::CppGenerator generator(parser, path, file_name, opts);
   return generator.generate();
 }
index d82ee5b..30aee22 100644 (file)
@@ -474,6 +474,7 @@ FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(2) Test FLATBUFFERS_FINAL_CLASS {
   int8_t padding0__;
 
  public:
+  struct Traits;
   static const flatbuffers::TypeTable *MiniReflectTypeTable() {
     return TestTypeTable();
   }
@@ -501,9 +502,28 @@ FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(2) Test FLATBUFFERS_FINAL_CLASS {
   void mutate_b(int8_t _b) {
     flatbuffers::WriteScalar(&b_, _b);
   }
+  template<size_t Index>
+  auto get_field() const {
+         if constexpr (Index == 0) return a();
+    else if constexpr (Index == 1) return b();
+    else static_assert(Index != Index, "Invalid Field Index");
+  }
 };
 FLATBUFFERS_STRUCT_END(Test, 4);
 
+struct Test::Traits {
+  using type = Test;
+  static constexpr auto name = "Test";
+  static constexpr auto fully_qualified_name = "MyGame.Example.Test";
+  static constexpr std::array<const char *, 2> field_names = {
+    "a",
+    "b"
+  };
+  template<size_t Index>
+  using FieldType = decltype(std::declval<type>().get_field<Index>());
+  static constexpr size_t fields_number = 2;
+};
+
 FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(8) Vec3 FLATBUFFERS_FINAL_CLASS {
  private:
   float x_;
@@ -517,6 +537,7 @@ FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(8) Vec3 FLATBUFFERS_FINAL_CLASS {
   int16_t padding2__;
 
  public:
+  struct Traits;
   static const flatbuffers::TypeTable *MiniReflectTypeTable() {
     return Vec3TypeTable();
   }
@@ -584,15 +605,43 @@ FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(8) Vec3 FLATBUFFERS_FINAL_CLASS {
   MyGame::Example::Test &mutable_test3() {
     return test3_;
   }
+  template<size_t Index>
+  auto get_field() const {
+         if constexpr (Index == 0) return x();
+    else if constexpr (Index == 1) return y();
+    else if constexpr (Index == 2) return z();
+    else if constexpr (Index == 3) return test1();
+    else if constexpr (Index == 4) return test2();
+    else if constexpr (Index == 5) return test3();
+    else static_assert(Index != Index, "Invalid Field Index");
+  }
 };
 FLATBUFFERS_STRUCT_END(Vec3, 32);
 
+struct Vec3::Traits {
+  using type = Vec3;
+  static constexpr auto name = "Vec3";
+  static constexpr auto fully_qualified_name = "MyGame.Example.Vec3";
+  static constexpr std::array<const char *, 6> field_names = {
+    "x",
+    "y",
+    "z",
+    "test1",
+    "test2",
+    "test3"
+  };
+  template<size_t Index>
+  using FieldType = decltype(std::declval<type>().get_field<Index>());
+  static constexpr size_t fields_number = 6;
+};
+
 FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) Ability FLATBUFFERS_FINAL_CLASS {
  private:
   uint32_t id_;
   uint32_t distance_;
 
  public:
+  struct Traits;
   static const flatbuffers::TypeTable *MiniReflectTypeTable() {
     return AbilityTypeTable();
   }
@@ -622,9 +671,28 @@ FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) Ability FLATBUFFERS_FINAL_CLASS {
   void mutate_distance(uint32_t _distance) {
     flatbuffers::WriteScalar(&distance_, _distance);
   }
+  template<size_t Index>
+  auto get_field() const {
+         if constexpr (Index == 0) return id();
+    else if constexpr (Index == 1) return distance();
+    else static_assert(Index != Index, "Invalid Field Index");
+  }
 };
 FLATBUFFERS_STRUCT_END(Ability, 8);
 
+struct Ability::Traits {
+  using type = Ability;
+  static constexpr auto name = "Ability";
+  static constexpr auto fully_qualified_name = "MyGame.Example.Ability";
+  static constexpr std::array<const char *, 2> field_names = {
+    "id",
+    "distance"
+  };
+  template<size_t Index>
+  using FieldType = decltype(std::declval<type>().get_field<Index>());
+  static constexpr size_t fields_number = 2;
+};
+
 }  // namespace Example
 
 struct InParentNamespaceT : public flatbuffers::NativeTable {
@@ -671,6 +739,10 @@ inline flatbuffers::Offset<InParentNamespace> CreateInParentNamespace(
 struct InParentNamespace::Traits {
   using type = InParentNamespace;
   static auto constexpr Create = CreateInParentNamespace;
+  static constexpr auto name = "InParentNamespace";
+  static constexpr auto fully_qualified_name = "MyGame.InParentNamespace";
+  static constexpr std::array<const char *, 0> field_names = {};
+  static constexpr size_t fields_number = 0;
 };
 
 flatbuffers::Offset<InParentNamespace> CreateInParentNamespace(flatbuffers::FlatBufferBuilder &_fbb, const InParentNamespaceT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr);
@@ -721,6 +793,10 @@ inline flatbuffers::Offset<Monster> CreateMonster(
 struct Monster::Traits {
   using type = Monster;
   static auto constexpr Create = CreateMonster;
+  static constexpr auto name = "Monster";
+  static constexpr auto fully_qualified_name = "MyGame.Example2.Monster";
+  static constexpr std::array<const char *, 0> field_names = {};
+  static constexpr size_t fields_number = 0;
 };
 
 flatbuffers::Offset<Monster> CreateMonster(flatbuffers::FlatBufferBuilder &_fbb, const MonsterT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr);
@@ -750,6 +826,11 @@ struct TestSimpleTableWithEnum FLATBUFFERS_FINAL_CLASS : private flatbuffers::Ta
   bool mutate_color(MyGame::Example::Color _color) {
     return SetField<uint8_t>(VT_COLOR, static_cast<uint8_t>(_color), 2);
   }
+  template<size_t Index>
+  auto get_field() const {
+         if constexpr (Index == 0) return color();
+    else static_assert(Index != Index, "Invalid Field Index");
+  }
   bool Verify(flatbuffers::Verifier &verifier) const {
     return VerifyTableStart(verifier) &&
            VerifyField<uint8_t>(verifier, VT_COLOR) &&
@@ -789,6 +870,14 @@ inline flatbuffers::Offset<TestSimpleTableWithEnum> CreateTestSimpleTableWithEnu
 struct TestSimpleTableWithEnum::Traits {
   using type = TestSimpleTableWithEnum;
   static auto constexpr Create = CreateTestSimpleTableWithEnum;
+  static constexpr auto name = "TestSimpleTableWithEnum";
+  static constexpr auto fully_qualified_name = "MyGame.Example.TestSimpleTableWithEnum";
+  static constexpr std::array<const char *, 1> field_names = {
+    "color"
+  };
+  template<size_t Index>
+  using FieldType = decltype(std::declval<type>().get_field<Index>());
+  static constexpr size_t fields_number = 1;
 };
 
 flatbuffers::Offset<TestSimpleTableWithEnum> CreateTestSimpleTableWithEnum(flatbuffers::FlatBufferBuilder &_fbb, const TestSimpleTableWithEnumT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr);
@@ -836,6 +925,13 @@ struct Stat FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
   int KeyCompareWithValue(uint16_t val) const {
     return static_cast<int>(count() > val) - static_cast<int>(count() < val);
   }
+  template<size_t Index>
+  auto get_field() const {
+         if constexpr (Index == 0) return id();
+    else if constexpr (Index == 1) return val();
+    else if constexpr (Index == 2) return count();
+    else static_assert(Index != Index, "Invalid Field Index");
+  }
   bool Verify(flatbuffers::Verifier &verifier) const {
     return VerifyTableStart(verifier) &&
            VerifyOffset(verifier, VT_ID) &&
@@ -888,6 +984,16 @@ inline flatbuffers::Offset<Stat> CreateStat(
 struct Stat::Traits {
   using type = Stat;
   static auto constexpr Create = CreateStat;
+  static constexpr auto name = "Stat";
+  static constexpr auto fully_qualified_name = "MyGame.Example.Stat";
+  static constexpr std::array<const char *, 3> field_names = {
+    "id",
+    "val",
+    "count"
+  };
+  template<size_t Index>
+  using FieldType = decltype(std::declval<type>().get_field<Index>());
+  static constexpr size_t fields_number = 3;
 };
 
 inline flatbuffers::Offset<Stat> CreateStatDirect(
@@ -932,6 +1038,11 @@ struct Referrable FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
   int KeyCompareWithValue(uint64_t val) const {
     return static_cast<int>(id() > val) - static_cast<int>(id() < val);
   }
+  template<size_t Index>
+  auto get_field() const {
+         if constexpr (Index == 0) return id();
+    else static_assert(Index != Index, "Invalid Field Index");
+  }
   bool Verify(flatbuffers::Verifier &verifier) const {
     return VerifyTableStart(verifier) &&
            VerifyField<uint64_t>(verifier, VT_ID) &&
@@ -971,6 +1082,14 @@ inline flatbuffers::Offset<Referrable> CreateReferrable(
 struct Referrable::Traits {
   using type = Referrable;
   static auto constexpr Create = CreateReferrable;
+  static constexpr auto name = "Referrable";
+  static constexpr auto fully_qualified_name = "MyGame.Example.Referrable";
+  static constexpr std::array<const char *, 1> field_names = {
+    "id"
+  };
+  template<size_t Index>
+  using FieldType = decltype(std::declval<type>().get_field<Index>());
+  static constexpr size_t fields_number = 1;
 };
 
 flatbuffers::Offset<Referrable> CreateReferrable(flatbuffers::FlatBufferBuilder &_fbb, const ReferrableT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr);
@@ -1423,6 +1542,60 @@ struct Monster FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
   flatbuffers::Vector<flatbuffers::Offset<MyGame::Example::Stat>> *mutable_scalar_key_sorted_tables() {
     return GetPointer<flatbuffers::Vector<flatbuffers::Offset<MyGame::Example::Stat>> *>(VT_SCALAR_KEY_SORTED_TABLES);
   }
+  template<size_t Index>
+  auto get_field() const {
+         if constexpr (Index == 0) return pos();
+    else if constexpr (Index == 1) return mana();
+    else if constexpr (Index == 2) return hp();
+    else if constexpr (Index == 3) return name();
+    else if constexpr (Index == 4) return inventory();
+    else if constexpr (Index == 5) return color();
+    else if constexpr (Index == 6) return test_type();
+    else if constexpr (Index == 7) return test();
+    else if constexpr (Index == 8) return test4();
+    else if constexpr (Index == 9) return testarrayofstring();
+    else if constexpr (Index == 10) return testarrayoftables();
+    else if constexpr (Index == 11) return enemy();
+    else if constexpr (Index == 12) return testnestedflatbuffer();
+    else if constexpr (Index == 13) return testempty();
+    else if constexpr (Index == 14) return testbool();
+    else if constexpr (Index == 15) return testhashs32_fnv1();
+    else if constexpr (Index == 16) return testhashu32_fnv1();
+    else if constexpr (Index == 17) return testhashs64_fnv1();
+    else if constexpr (Index == 18) return testhashu64_fnv1();
+    else if constexpr (Index == 19) return testhashs32_fnv1a();
+    else if constexpr (Index == 20) return testhashu32_fnv1a();
+    else if constexpr (Index == 21) return testhashs64_fnv1a();
+    else if constexpr (Index == 22) return testhashu64_fnv1a();
+    else if constexpr (Index == 23) return testarrayofbools();
+    else if constexpr (Index == 24) return testf();
+    else if constexpr (Index == 25) return testf2();
+    else if constexpr (Index == 26) return testf3();
+    else if constexpr (Index == 27) return testarrayofstring2();
+    else if constexpr (Index == 28) return testarrayofsortedstruct();
+    else if constexpr (Index == 29) return flex();
+    else if constexpr (Index == 30) return test5();
+    else if constexpr (Index == 31) return vector_of_longs();
+    else if constexpr (Index == 32) return vector_of_doubles();
+    else if constexpr (Index == 33) return parent_namespace_test();
+    else if constexpr (Index == 34) return vector_of_referrables();
+    else if constexpr (Index == 35) return single_weak_reference();
+    else if constexpr (Index == 36) return vector_of_weak_references();
+    else if constexpr (Index == 37) return vector_of_strong_referrables();
+    else if constexpr (Index == 38) return co_owning_reference();
+    else if constexpr (Index == 39) return vector_of_co_owning_references();
+    else if constexpr (Index == 40) return non_owning_reference();
+    else if constexpr (Index == 41) return vector_of_non_owning_references();
+    else if constexpr (Index == 42) return any_unique_type();
+    else if constexpr (Index == 43) return any_unique();
+    else if constexpr (Index == 44) return any_ambiguous_type();
+    else if constexpr (Index == 45) return any_ambiguous();
+    else if constexpr (Index == 46) return vector_of_enums();
+    else if constexpr (Index == 47) return signed_enum();
+    else if constexpr (Index == 48) return testrequirednestedflatbuffer();
+    else if constexpr (Index == 49) return scalar_key_sorted_tables();
+    else static_assert(Index != Index, "Invalid Field Index");
+  }
   bool Verify(flatbuffers::Verifier &verifier) const {
     return VerifyTableStart(verifier) &&
            VerifyField<MyGame::Example::Vec3>(verifier, VT_POS) &&
@@ -1814,6 +1987,63 @@ inline flatbuffers::Offset<Monster> CreateMonster(
 struct Monster::Traits {
   using type = Monster;
   static auto constexpr Create = CreateMonster;
+  static constexpr auto name = "Monster";
+  static constexpr auto fully_qualified_name = "MyGame.Example.Monster";
+  static constexpr std::array<const char *, 50> field_names = {
+    "pos",
+    "mana",
+    "hp",
+    "name",
+    "inventory",
+    "color",
+    "test_type",
+    "test",
+    "test4",
+    "testarrayofstring",
+    "testarrayoftables",
+    "enemy",
+    "testnestedflatbuffer",
+    "testempty",
+    "testbool",
+    "testhashs32_fnv1",
+    "testhashu32_fnv1",
+    "testhashs64_fnv1",
+    "testhashu64_fnv1",
+    "testhashs32_fnv1a",
+    "testhashu32_fnv1a",
+    "testhashs64_fnv1a",
+    "testhashu64_fnv1a",
+    "testarrayofbools",
+    "testf",
+    "testf2",
+    "testf3",
+    "testarrayofstring2",
+    "testarrayofsortedstruct",
+    "flex",
+    "test5",
+    "vector_of_longs",
+    "vector_of_doubles",
+    "parent_namespace_test",
+    "vector_of_referrables",
+    "single_weak_reference",
+    "vector_of_weak_references",
+    "vector_of_strong_referrables",
+    "co_owning_reference",
+    "vector_of_co_owning_references",
+    "non_owning_reference",
+    "vector_of_non_owning_references",
+    "any_unique_type",
+    "any_unique",
+    "any_ambiguous_type",
+    "any_ambiguous",
+    "vector_of_enums",
+    "signed_enum",
+    "testrequirednestedflatbuffer",
+    "scalar_key_sorted_tables"
+  };
+  template<size_t Index>
+  using FieldType = decltype(std::declval<type>().get_field<Index>());
+  static constexpr size_t fields_number = 50;
 };
 
 inline flatbuffers::Offset<Monster> CreateMonsterDirect(
@@ -2054,6 +2284,22 @@ struct TypeAliases FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
   flatbuffers::Vector<double> *mutable_vf64() {
     return GetPointer<flatbuffers::Vector<double> *>(VT_VF64);
   }
+  template<size_t Index>
+  auto get_field() const {
+         if constexpr (Index == 0) return i8();
+    else if constexpr (Index == 1) return u8();
+    else if constexpr (Index == 2) return i16();
+    else if constexpr (Index == 3) return u16();
+    else if constexpr (Index == 4) return i32();
+    else if constexpr (Index == 5) return u32();
+    else if constexpr (Index == 6) return i64();
+    else if constexpr (Index == 7) return u64();
+    else if constexpr (Index == 8) return f32();
+    else if constexpr (Index == 9) return f64();
+    else if constexpr (Index == 10) return v8();
+    else if constexpr (Index == 11) return vf64();
+    else static_assert(Index != Index, "Invalid Field Index");
+  }
   bool Verify(flatbuffers::Verifier &verifier) const {
     return VerifyTableStart(verifier) &&
            VerifyField<int8_t>(verifier, VT_I8) &&
@@ -2161,6 +2407,25 @@ inline flatbuffers::Offset<TypeAliases> CreateTypeAliases(
 struct TypeAliases::Traits {
   using type = TypeAliases;
   static auto constexpr Create = CreateTypeAliases;
+  static constexpr auto name = "TypeAliases";
+  static constexpr auto fully_qualified_name = "MyGame.Example.TypeAliases";
+  static constexpr std::array<const char *, 12> field_names = {
+    "i8",
+    "u8",
+    "i16",
+    "u16",
+    "i32",
+    "u32",
+    "i64",
+    "u64",
+    "f32",
+    "f64",
+    "v8",
+    "vf64"
+  };
+  template<size_t Index>
+  using FieldType = decltype(std::declval<type>().get_field<Index>());
+  static constexpr size_t fields_number = 12;
 };
 
 inline flatbuffers::Offset<TypeAliases> CreateTypeAliasesDirect(
index 7581e38..4e99e7c 100644 (file)
@@ -348,6 +348,46 @@ struct ScalarStuff FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
   bool mutate_default_enum(optional_scalars::OptionalByte _default_enum) {
     return SetField<int8_t>(VT_DEFAULT_ENUM, static_cast<int8_t>(_default_enum), 1);
   }
+  template<size_t Index>
+  auto get_field() const {
+         if constexpr (Index == 0) return just_i8();
+    else if constexpr (Index == 1) return maybe_i8();
+    else if constexpr (Index == 2) return default_i8();
+    else if constexpr (Index == 3) return just_u8();
+    else if constexpr (Index == 4) return maybe_u8();
+    else if constexpr (Index == 5) return default_u8();
+    else if constexpr (Index == 6) return just_i16();
+    else if constexpr (Index == 7) return maybe_i16();
+    else if constexpr (Index == 8) return default_i16();
+    else if constexpr (Index == 9) return just_u16();
+    else if constexpr (Index == 10) return maybe_u16();
+    else if constexpr (Index == 11) return default_u16();
+    else if constexpr (Index == 12) return just_i32();
+    else if constexpr (Index == 13) return maybe_i32();
+    else if constexpr (Index == 14) return default_i32();
+    else if constexpr (Index == 15) return just_u32();
+    else if constexpr (Index == 16) return maybe_u32();
+    else if constexpr (Index == 17) return default_u32();
+    else if constexpr (Index == 18) return just_i64();
+    else if constexpr (Index == 19) return maybe_i64();
+    else if constexpr (Index == 20) return default_i64();
+    else if constexpr (Index == 21) return just_u64();
+    else if constexpr (Index == 22) return maybe_u64();
+    else if constexpr (Index == 23) return default_u64();
+    else if constexpr (Index == 24) return just_f32();
+    else if constexpr (Index == 25) return maybe_f32();
+    else if constexpr (Index == 26) return default_f32();
+    else if constexpr (Index == 27) return just_f64();
+    else if constexpr (Index == 28) return maybe_f64();
+    else if constexpr (Index == 29) return default_f64();
+    else if constexpr (Index == 30) return just_bool();
+    else if constexpr (Index == 31) return maybe_bool();
+    else if constexpr (Index == 32) return default_bool();
+    else if constexpr (Index == 33) return just_enum();
+    else if constexpr (Index == 34) return maybe_enum();
+    else if constexpr (Index == 35) return default_enum();
+    else static_assert(Index != Index, "Invalid Field Index");
+  }
   bool Verify(flatbuffers::Verifier &verifier) const {
     return VerifyTableStart(verifier) &&
            VerifyField<int8_t>(verifier, VT_JUST_I8) &&
@@ -597,6 +637,49 @@ inline flatbuffers::Offset<ScalarStuff> CreateScalarStuff(
 struct ScalarStuff::Traits {
   using type = ScalarStuff;
   static auto constexpr Create = CreateScalarStuff;
+  static constexpr auto name = "ScalarStuff";
+  static constexpr auto fully_qualified_name = "optional_scalars.ScalarStuff";
+  static constexpr std::array<const char *, 36> field_names = {
+    "just_i8",
+    "maybe_i8",
+    "default_i8",
+    "just_u8",
+    "maybe_u8",
+    "default_u8",
+    "just_i16",
+    "maybe_i16",
+    "default_i16",
+    "just_u16",
+    "maybe_u16",
+    "default_u16",
+    "just_i32",
+    "maybe_i32",
+    "default_i32",
+    "just_u32",
+    "maybe_u32",
+    "default_u32",
+    "just_i64",
+    "maybe_i64",
+    "default_i64",
+    "just_u64",
+    "maybe_u64",
+    "default_u64",
+    "just_f32",
+    "maybe_f32",
+    "default_f32",
+    "just_f64",
+    "maybe_f64",
+    "default_f64",
+    "just_bool",
+    "maybe_bool",
+    "default_bool",
+    "just_enum",
+    "maybe_enum",
+    "default_enum"
+  };
+  template<size_t Index>
+  using FieldType = decltype(std::declval<type>().get_field<Index>());
+  static constexpr size_t fields_number = 36;
 };
 
 flatbuffers::Offset<ScalarStuff> CreateScalarStuff(flatbuffers::FlatBufferBuilder &_fbb, const ScalarStuffT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr);
diff --git a/tests/cpp17/stringify_util.h b/tests/cpp17/stringify_util.h
new file mode 100644 (file)
index 0000000..dc94722
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2020 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This contains some utilities/examples for how to leverage the static reflec-
+// tion features of tables and structs in the C++17 code generation to recur-
+// sively produce a string representation of any Flatbuffer table or struct use
+// compile-time iteration over the fields. Note that this code is completely
+// generic in that it makes no reference to any particular Flatbuffer type.
+
+#include <optional>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "flatbuffers/flatbuffers.h"
+#include "flatbuffers/util.h"
+
+namespace cpp17 {
+
+// User calls this; need to forward declare it since it is called recursively.
+template<typename T>
+std::optional<std::string> StringifyFlatbufferValue(
+    T &&val, const std::string &indent = "");
+
+namespace detail {
+
+/*******************************************************************************
+** Metaprogramming helpers for detecting Flatbuffers Tables, Structs, & Vectors.
+*******************************************************************************/
+template<typename FBS, typename = void>
+struct is_flatbuffers_table_or_struct : std::false_type {};
+
+// We know it's a table or struct when it has a Traits subclass.
+template<typename FBS>
+struct is_flatbuffers_table_or_struct<FBS, std::void_t<typename FBS::Traits>>
+    : std::true_type {};
+
+template<typename FBS>
+inline constexpr bool is_flatbuffers_table_or_struct_v =
+    is_flatbuffers_table_or_struct<FBS>::value;
+
+template<typename T> struct is_flatbuffers_vector : std::false_type {};
+
+template<typename T>
+struct is_flatbuffers_vector<flatbuffers::Vector<T>> : std::true_type {};
+
+template<typename T>
+inline constexpr bool is_flatbuffers_vector_v = is_flatbuffers_vector<T>::value;
+
+/*******************************************************************************
+** Compile-time Iteration & Recursive Stringification over Flatbuffers types.
+*******************************************************************************/
+template<size_t Index, typename FBS>
+std::string AddStringifiedField(const FBS &fbs, const std::string &indent) {
+  auto value_string =
+      StringifyFlatbufferValue(fbs.template get_field<Index>(), indent);
+  if (!value_string) { return ""; }
+  return indent + FBS::Traits::field_names[Index] + " = " + *value_string +
+         "\n";
+}
+
+template<typename FBS, size_t... Indexes>
+std::string StringifyTableOrStructImpl(const FBS &fbs,
+                                       const std::string &indent,
+                                       std::index_sequence<Indexes...>) {
+  // This line is where the compile-time iteration happens!
+  return (AddStringifiedField<Indexes>(fbs, indent) + ...);
+}
+
+template<typename FBS>
+std::string StringifyTableOrStruct(const FBS &fbs, const std::string &indent) {
+  static constexpr size_t field_count = FBS::Traits::fields_number;
+  std::string out;
+  if constexpr (field_count > 0) {
+    out = std::string(FBS::Traits::fully_qualified_name) + "{\n" +
+          StringifyTableOrStructImpl(fbs, indent + "  ",
+                                     std::make_index_sequence<field_count>{}) +
+          indent + '}';
+  }
+  return out;
+}
+
+template<typename T>
+std::string StringifyVector(const flatbuffers::Vector<T> &vec,
+                            const std::string &indent) {
+  const auto prologue = indent + std::string("  ");
+  const auto epilogue = std::string(",\n");
+  std::string text;
+  text += "[\n";
+  for (auto it = vec.cbegin(), end = vec.cend(); it != end; ++it) {
+    text += prologue;
+    text += StringifyFlatbufferValue(*it).value_or("(field absent)");
+    text += epilogue;
+  }
+  if (vec.cbegin() != vec.cend()) {
+    text.resize(text.size() - epilogue.size());
+  }
+  text += '\n' + indent + ']';
+  return text;
+}
+
+template<typename T> std::string StringifyArithmeticType(T val) {
+  return flatbuffers::NumToString(val);
+}
+
+}  // namespace detail
+
+/*******************************************************************************
+** Take any flatbuffer type (table, struct, Vector, int...) and stringify it.
+*******************************************************************************/
+template<typename T>
+std::optional<std::string> StringifyFlatbufferValue(T &&val,
+                                                    const std::string &indent) {
+  constexpr bool is_pointer = std::is_pointer_v<std::remove_reference_t<T>>;
+  if constexpr (is_pointer) {
+    if (val == nullptr) return std::nullopt;  // Field is absent.
+  }
+  using decayed =
+      std::decay_t<std::remove_pointer_t<std::remove_reference_t<T>>>;
+
+  // Is it a Flatbuffers Table or Struct?
+  if constexpr (detail::is_flatbuffers_table_or_struct_v<decayed>) {
+    // We have a nested table or struct; use recursion!
+    if constexpr (is_pointer)
+      return detail::StringifyTableOrStruct(*val, indent);
+    else
+      return detail::StringifyTableOrStruct(val, indent);
+  }
+
+  // Is it an 8-bit number?  If so, print it like an int (not char).
+  else if constexpr (std::is_same_v<decayed, int8_t> ||
+                     std::is_same_v<decayed, uint8_t>) {
+    return detail::StringifyArithmeticType(static_cast<int>(val));
+  }
+
+  // Is it an enum? If so, print it like an int, since Flatbuffers doesn't yet
+  // have type-based reflection for enums, so we can't print the enum's name :(
+  else if constexpr (std::is_enum_v<decayed>) {
+    return StringifyFlatbufferValue(
+        static_cast<std::underlying_type_t<decayed>>(val), indent);
+  }
+
+  // Is it an int, double, float, uint32_t, etc.?
+  else if constexpr (std::is_arithmetic_v<decayed>) {
+    return detail::StringifyArithmeticType(val);
+  }
+
+  // Is it a Flatbuffers string?
+  else if constexpr (std::is_same_v<decayed, flatbuffers::String>) {
+    return '"' + val->str() + '"';
+  }
+
+  // Is it a Flatbuffers Vector?
+  else if constexpr (detail::is_flatbuffers_vector_v<decayed>) {
+    return detail::StringifyVector(*val, indent);
+  }
+
+  // Is it a void pointer?
+  else if constexpr (std::is_same_v<decayed, void>) {
+    // Can't format it.
+    return std::nullopt;
+  }
+
+  else {
+    // Not sure how to format this type, whatever it is.
+    static_assert(sizeof(T) != sizeof(T),
+                  "Do not know how to format this type T (the compiler error "
+                  "should tell you nearby what T is).");
+  }
+}
+
+}  // namespace cpp17
index 9b47c10..47083d3 100644 (file)
@@ -25,6 +25,7 @@
 #include "flatbuffers/minireflect.h"
 #include "flatbuffers/registry.h"
 #include "flatbuffers/util.h"
+#include "stringify_util.h"
 #include "test_assert.h"
 
 // Embed generated code into an isolated namespace.
@@ -38,6 +39,144 @@ namespace cpp11 {
 #include "../optional_scalars_generated.h"
 }  // namespace cpp11
 
+using ::cpp17::MyGame::Example::Monster;
+using ::cpp17::MyGame::Example::Vec3;
+
+/*******************************************************************************
+** Build some FB objects.
+*******************************************************************************/
+const Monster *BuildMonster(flatbuffers::FlatBufferBuilder &fbb) {
+  using ::cpp17::MyGame::Example::Color;
+  using ::cpp17::MyGame::Example::MonsterBuilder;
+  using ::cpp17::MyGame::Example::Test;
+  auto name = fbb.CreateString("my_monster");
+  auto inventory = fbb.CreateVector(std::vector<uint8_t>{ 4, 5, 6, 7 });
+  MonsterBuilder builder(fbb);
+  auto vec3 = Vec3{ /*x=*/1.1f,
+                    /*y=*/2.2f,
+                    /*z=*/3.3f,
+                    /*test1=*/6.6,
+                    /*test2=*/Color::Green,
+                    /*test3=*/
+                    Test(
+                        /*a=*/11,
+                        /*b=*/90) };
+  builder.add_pos(&vec3);
+  builder.add_name(name);
+  builder.add_mana(1);
+  builder.add_hp(2);
+  builder.add_testbool(true);
+  builder.add_testhashs32_fnv1(4);
+  builder.add_testhashu32_fnv1(5);
+  builder.add_testhashs64_fnv1(6);
+  builder.add_testhashu64_fnv1(7);
+  builder.add_testhashs32_fnv1a(8);
+  builder.add_testhashu32_fnv1a(9);
+  builder.add_testhashs64_fnv1a(10);
+  builder.add_testhashu64_fnv1a(11);
+  builder.add_testf(12.1f);
+  builder.add_testf2(13.1f);
+  builder.add_testf3(14.1f);
+  builder.add_single_weak_reference(15);
+  builder.add_co_owning_reference(16);
+  builder.add_non_owning_reference(17);
+  builder.add_inventory(inventory);
+  fbb.Finish(builder.Finish());
+  const Monster *monster =
+      flatbuffers::GetRoot<Monster>(fbb.GetBufferPointer());
+  return monster;
+}
+
+/*******************************************************************************
+** Test Case: Static Field Reflection Traits for Table & Structs.
+*******************************************************************************/
+// This test tests & demonstrates the power of the static reflection. Using it,
+// we can given any Flatbuffer type to a generic function and it will be able to
+// produce is full recursive string representation of it.
+//
+// This test covers all types: primitive types, structs, tables, Vectors, etc.
+//
+void StringifyAnyFlatbuffersTypeTest() {
+  flatbuffers::FlatBufferBuilder fbb;
+  // We are using a Monster here, but we could have used any type, because the
+  // code that follows is totally generic!
+  const auto *monster = BuildMonster(fbb);
+
+  std::string expected = R"(MyGame.Example.Monster{
+        pos = MyGame.Example.Vec3{
+          x = 1.1
+          y = 2.2
+          z = 3.3
+          test1 = 6.6
+          test2 = 2
+          test3 = MyGame.Example.Test{
+            a = 11
+            b = 90
+          }
+        }
+        mana = 1
+        hp = 2
+        name = "my_monster"
+        inventory = [
+          4,
+          5,
+          6,
+          7
+        ]
+        color = 8
+        test_type = 0
+        testbool = 1
+        testhashs32_fnv1 = 4
+        testhashu32_fnv1 = 5
+        testhashs64_fnv1 = 6
+        testhashu64_fnv1 = 7
+        testhashs32_fnv1a = 8
+        testhashu32_fnv1a = 9
+        testhashs64_fnv1a = 10
+        testhashu64_fnv1a = 11
+        testf = 12.1
+        testf2 = 13.1
+        testf3 = 14.1
+        single_weak_reference = 15
+        co_owning_reference = 16
+        non_owning_reference = 17
+        any_unique_type = 0
+        any_ambiguous_type = 0
+        signed_enum = -1
+      })";
+
+  // Call a generic function that has no specific knowledge of the flatbuffer we
+  // are passing in; it should use only static reflection to produce a string
+  // representations of the field names and values recursively. We give it an
+  // initial indentation so that the result can be compared with our raw string
+  // above, which we wanted to indent so that it will look nicer in this code.
+  //
+  // A note about JSON: as can be seen from the string above, this produces a
+  // JSON-like notation, but we are not using any of Flatbuffers' JSON infra to
+  // produce this! It is produced entirely using compile-time reflection, and
+  // thus does not require any runtime access to the *.fbs definition files!
+  std::optional<std::string> result =
+      cpp17::StringifyFlatbufferValue(*monster, /*indent=*/"      ");
+
+  TEST_ASSERT(result.has_value());
+  TEST_EQ_STR(expected.c_str(), result->c_str());
+}
+
+/*******************************************************************************
+** Test Traits::FieldType
+*******************************************************************************/
+using pos_type = Monster::Traits::FieldType<0>;
+static_assert(std::is_same_v<pos_type, const Vec3*>);
+
+using mana_type = Monster::Traits::FieldType<1>;
+static_assert(std::is_same_v<mana_type, int16_t>);
+
+using name_type = Monster::Traits::FieldType<3>;
+static_assert(std::is_same_v<name_type, const flatbuffers::String*>);
+
+/*******************************************************************************
+** Generic Create Function Test.
+*******************************************************************************/
 void CreateTableByTypeTest() {
   flatbuffers::FlatBufferBuilder builder;
 
@@ -62,7 +201,8 @@ void CreateTableByTypeTest() {
 }
 
 void OptionalScalarsTest() {
-  static_assert(std::is_same<flatbuffers::Optional<float>, std::optional<float>>::value);
+  static_assert(
+      std::is_same<flatbuffers::Optional<float>, std::optional<float>>::value);
   static_assert(std::is_same<flatbuffers::nullopt_t, std::nullopt_t>::value);
 
   // test C++ nullable
@@ -105,6 +245,7 @@ void OptionalScalarsTest() {
 int FlatBufferCpp17Tests() {
   CreateTableByTypeTest();
   OptionalScalarsTest();
+  StringifyAnyFlatbuffersTypeTest();
   return 0;
 }
 
index 45c7ccd..4f6b437 100755 (executable)
@@ -88,7 +88,7 @@ else
 fi
 
 # Flag c++17 requires Clang6, GCC7, MSVC2017 (_MSC_VER >= 1914)  or higher.
-TEST_CPP17_FLAGS="--cpp --cpp-std c++17 -o ./cpp17/generated_cpp17 $TEST_NOINCL_FLAGS"
+TEST_CPP17_FLAGS="--cpp --cpp-std c++17 --cpp-static-reflection -o ./cpp17/generated_cpp17 $TEST_NOINCL_FLAGS"
 ../flatc $TEST_CPP17_FLAGS -I include_test monster_test.fbs
 ../flatc $TEST_CPP17_FLAGS optional_scalars.fbs