[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 <tuple> include.
This commit is contained in:
David P. Sicilia
2021-03-05 13:01:40 -05:00
committed by GitHub
parent 4033ff5892
commit a69815f72c
9 changed files with 834 additions and 11 deletions

View 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;
}