[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

@@ -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();
}