From a69815f72c7432a838f7163ecdcf6b30c8b3bf35 Mon Sep 17 00:00:00 2001 From: "David P. Sicilia" Date: Fri, 5 Mar 2021 13:01:40 -0500 Subject: [PATCH] [C++17] Add compile-time reflection for fields. (#6324) * [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 include. --- .github/workflows/build.yml | 2 +- include/flatbuffers/idl.h | 2 + src/flatc.cpp | 5 + src/idl_gen_cpp.cpp | 157 +++++++++++- .../cpp17/generated_cpp17/monster_test_generated.h | 265 +++++++++++++++++++++ .../generated_cpp17/optional_scalars_generated.h | 83 +++++++ tests/cpp17/stringify_util.h | 186 +++++++++++++++ tests/cpp17/test_cpp17.cpp | 143 ++++++++++- tests/generate_code.sh | 2 +- 9 files changed, 834 insertions(+), 11 deletions(-) create mode 100644 tests/cpp17/stringify_util.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7b01c98..12f046e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h index 0e9a3bc..d9ef95b 100644 --- a/include/flatbuffers/idl.h +++ b/include/flatbuffers/idl.h @@ -588,6 +588,7 @@ struct IDLOptions { bool cs_gen_json_serializer; std::vector 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), diff --git a/src/flatc.cpp b/src/flatc.cpp index 6eeefab..221b886 100644 --- a/src/flatc.cpp +++ b/src/flatc.cpp @@ -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 || diff --git a/src/idl_gen_cpp.cpp b/src/idl_gen_cpp.cpp index 5ac5f7c..d9f495b 100644 --- a/src/idl_gen_cpp.cpp +++ b/src/idl_gen_cpp.cpp @@ -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"; + code_ += " using FieldType = \\"; + code_ += "decltype(std::declval().get_field());"; + } + + void GenIndexBasedFieldGetter(const StructDef &struct_def) { + if (struct_def.fields.vec.empty()) { return; } + code_ += " template"; + 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(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 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(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(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(); } diff --git a/tests/cpp17/generated_cpp17/monster_test_generated.h b/tests/cpp17/generated_cpp17/monster_test_generated.h index d82ee5b..30aee22 100644 --- a/tests/cpp17/generated_cpp17/monster_test_generated.h +++ b/tests/cpp17/generated_cpp17/monster_test_generated.h @@ -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 + 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 field_names = { + "a", + "b" + }; + template + using FieldType = decltype(std::declval().get_field()); + 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 + 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 field_names = { + "x", + "y", + "z", + "test1", + "test2", + "test3" + }; + template + using FieldType = decltype(std::declval().get_field()); + 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 + 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 field_names = { + "id", + "distance" + }; + template + using FieldType = decltype(std::declval().get_field()); + static constexpr size_t fields_number = 2; +}; + } // namespace Example struct InParentNamespaceT : public flatbuffers::NativeTable { @@ -671,6 +739,10 @@ inline flatbuffers::Offset 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 field_names = {}; + static constexpr size_t fields_number = 0; }; flatbuffers::Offset CreateInParentNamespace(flatbuffers::FlatBufferBuilder &_fbb, const InParentNamespaceT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); @@ -721,6 +793,10 @@ inline flatbuffers::Offset 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 field_names = {}; + static constexpr size_t fields_number = 0; }; flatbuffers::Offset 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(VT_COLOR, static_cast(_color), 2); } + template + 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(verifier, VT_COLOR) && @@ -789,6 +870,14 @@ inline flatbuffers::Offset 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 field_names = { + "color" + }; + template + using FieldType = decltype(std::declval().get_field()); + static constexpr size_t fields_number = 1; }; flatbuffers::Offset 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(count() > val) - static_cast(count() < val); } + template + 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 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 field_names = { + "id", + "val", + "count" + }; + template + using FieldType = decltype(std::declval().get_field()); + static constexpr size_t fields_number = 3; }; inline flatbuffers::Offset CreateStatDirect( @@ -932,6 +1038,11 @@ struct Referrable FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { int KeyCompareWithValue(uint64_t val) const { return static_cast(id() > val) - static_cast(id() < val); } + template + 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(verifier, VT_ID) && @@ -971,6 +1082,14 @@ inline flatbuffers::Offset 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 field_names = { + "id" + }; + template + using FieldType = decltype(std::declval().get_field()); + static constexpr size_t fields_number = 1; }; flatbuffers::Offset 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> *mutable_scalar_key_sorted_tables() { return GetPointer> *>(VT_SCALAR_KEY_SORTED_TABLES); } + template + 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(verifier, VT_POS) && @@ -1814,6 +1987,63 @@ inline flatbuffers::Offset 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 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 + using FieldType = decltype(std::declval().get_field()); + static constexpr size_t fields_number = 50; }; inline flatbuffers::Offset CreateMonsterDirect( @@ -2054,6 +2284,22 @@ struct TypeAliases FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { flatbuffers::Vector *mutable_vf64() { return GetPointer *>(VT_VF64); } + template + 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(verifier, VT_I8) && @@ -2161,6 +2407,25 @@ inline flatbuffers::Offset 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 field_names = { + "i8", + "u8", + "i16", + "u16", + "i32", + "u32", + "i64", + "u64", + "f32", + "f64", + "v8", + "vf64" + }; + template + using FieldType = decltype(std::declval().get_field()); + static constexpr size_t fields_number = 12; }; inline flatbuffers::Offset CreateTypeAliasesDirect( diff --git a/tests/cpp17/generated_cpp17/optional_scalars_generated.h b/tests/cpp17/generated_cpp17/optional_scalars_generated.h index 7581e38..4e99e7c 100644 --- a/tests/cpp17/generated_cpp17/optional_scalars_generated.h +++ b/tests/cpp17/generated_cpp17/optional_scalars_generated.h @@ -348,6 +348,46 @@ struct ScalarStuff FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { bool mutate_default_enum(optional_scalars::OptionalByte _default_enum) { return SetField(VT_DEFAULT_ENUM, static_cast(_default_enum), 1); } + template + 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(verifier, VT_JUST_I8) && @@ -597,6 +637,49 @@ inline flatbuffers::Offset 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 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 + using FieldType = decltype(std::declval().get_field()); + static constexpr size_t fields_number = 36; }; flatbuffers::Offset 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 index 0000000..dc94722 --- /dev/null +++ b/tests/cpp17/stringify_util.h @@ -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 +#include +#include +#include +#include + +#include "flatbuffers/flatbuffers.h" +#include "flatbuffers/util.h" + +namespace cpp17 { + +// User calls this; need to forward declare it since it is called recursively. +template +std::optional StringifyFlatbufferValue( + T &&val, const std::string &indent = ""); + +namespace detail { + +/******************************************************************************* +** Metaprogramming helpers for detecting Flatbuffers Tables, Structs, & Vectors. +*******************************************************************************/ +template +struct is_flatbuffers_table_or_struct : std::false_type {}; + +// We know it's a table or struct when it has a Traits subclass. +template +struct is_flatbuffers_table_or_struct> + : std::true_type {}; + +template +inline constexpr bool is_flatbuffers_table_or_struct_v = + is_flatbuffers_table_or_struct::value; + +template struct is_flatbuffers_vector : std::false_type {}; + +template +struct is_flatbuffers_vector> : std::true_type {}; + +template +inline constexpr bool is_flatbuffers_vector_v = is_flatbuffers_vector::value; + +/******************************************************************************* +** Compile-time Iteration & Recursive Stringification over Flatbuffers types. +*******************************************************************************/ +template +std::string AddStringifiedField(const FBS &fbs, const std::string &indent) { + auto value_string = + StringifyFlatbufferValue(fbs.template get_field(), indent); + if (!value_string) { return ""; } + return indent + FBS::Traits::field_names[Index] + " = " + *value_string + + "\n"; +} + +template +std::string StringifyTableOrStructImpl(const FBS &fbs, + const std::string &indent, + std::index_sequence) { + // This line is where the compile-time iteration happens! + return (AddStringifiedField(fbs, indent) + ...); +} + +template +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{}) + + indent + '}'; + } + return out; +} + +template +std::string StringifyVector(const flatbuffers::Vector &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 std::string StringifyArithmeticType(T val) { + return flatbuffers::NumToString(val); +} + +} // namespace detail + +/******************************************************************************* +** Take any flatbuffer type (table, struct, Vector, int...) and stringify it. +*******************************************************************************/ +template +std::optional StringifyFlatbufferValue(T &&val, + const std::string &indent) { + constexpr bool is_pointer = std::is_pointer_v>; + if constexpr (is_pointer) { + if (val == nullptr) return std::nullopt; // Field is absent. + } + using decayed = + std::decay_t>>; + + // Is it a Flatbuffers Table or Struct? + if constexpr (detail::is_flatbuffers_table_or_struct_v) { + // 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 || + std::is_same_v) { + return detail::StringifyArithmeticType(static_cast(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) { + return StringifyFlatbufferValue( + static_cast>(val), indent); + } + + // Is it an int, double, float, uint32_t, etc.? + else if constexpr (std::is_arithmetic_v) { + return detail::StringifyArithmeticType(val); + } + + // Is it a Flatbuffers string? + else if constexpr (std::is_same_v) { + return '"' + val->str() + '"'; + } + + // Is it a Flatbuffers Vector? + else if constexpr (detail::is_flatbuffers_vector_v) { + return detail::StringifyVector(*val, indent); + } + + // Is it a void pointer? + else if constexpr (std::is_same_v) { + // 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 diff --git a/tests/cpp17/test_cpp17.cpp b/tests/cpp17/test_cpp17.cpp index 9b47c10..47083d3 100644 --- a/tests/cpp17/test_cpp17.cpp +++ b/tests/cpp17/test_cpp17.cpp @@ -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{ 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(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 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); + +using mana_type = Monster::Traits::FieldType<1>; +static_assert(std::is_same_v); + +using name_type = Monster::Traits::FieldType<3>; +static_assert(std::is_same_v); + +/******************************************************************************* +** Generic Create Function Test. +*******************************************************************************/ void CreateTableByTypeTest() { flatbuffers::FlatBufferBuilder builder; @@ -62,7 +201,8 @@ void CreateTableByTypeTest() { } void OptionalScalarsTest() { - static_assert(std::is_same, std::optional>::value); + static_assert( + std::is_same, std::optional>::value); static_assert(std::is_same::value); // test C++ nullable @@ -105,6 +245,7 @@ void OptionalScalarsTest() { int FlatBufferCpp17Tests() { CreateTableByTypeTest(); OptionalScalarsTest(); + StringifyAnyFlatbuffersTypeTest(); return 0; } diff --git a/tests/generate_code.sh b/tests/generate_code.sh index 45c7ccd..4f6b437 100755 --- a/tests/generate_code.sh +++ b/tests/generate_code.sh @@ -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 -- 2.7.4