-
Notifications
You must be signed in to change notification settings - Fork 3.5k
[C++17] Add compile-time reflection for fields. #6324
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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 google#6285.
|
@aardappel @vglavnyy Please take a look; this should be exactly what we talked about in #6285. The one thing that I wasn't sure about was the concern that you both raised that it "would be good to ensure fields_pack never constructs anything"; the Thanks in advance for your consideration :) |
|
As you might be aware, there are a bunch of diffs to generated python files that are unrelated to my change... perhaps someone forgot to commit them recently? As far as I can see, all of the CI checks that are failing are doing so only for that reason. See PR #6325. |
aardappel
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally looks very good to me!
| * limitations under the License. | ||
| */ | ||
|
|
||
| // This contains some utilities/examples for how to leverage the static reflec- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great for testing.. I wonder if some of it can be generic enough that its worth sticking in include/flatbuffers ? Wonder what happens if someone actually wants to use this functionality, right now they'd probably to copy this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes this is a standalone utility that someone could include and use in their own project if they are compiling with C++17. If you like I can move it into the include/flatbuffers folder so that they can include it more easily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vglavnyy do you think that makes sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't mind. However, we already have a lot of changes in C++ code: optional, span, --cpp-std c++11.
Maybe move it into the include folder after release?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok for now I will just leave it in the test folder, and we can move it at a later time.
Another thing to think about -- it is possible to parse this string and produce a real object with roughly the same amount of code/methodology. Perhaps we might want to include that as well when the time comes.
Another use case of this reflection is that we can provide a generic function called FlatbuffersEquals that will compare two Flatbuffers of the same type and tell you if they are equal or not. It will do this by iterating through the fields and comparing them, again using the static reflection. I believe traditionally this has not been possible to compare two buffers right? Or can you just do a memcmp() on the binary data? IIUC that would only work if the fields were added in the same order when creating the buffers?
| } | ||
|
|
||
| const Monster* BuildMonster(flatbuffers::FlatBufferBuilder& fbb) { | ||
| using ::cpp17::MyGame::Example::MonsterBuilder; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need another function creating a buffer? don't we have existing buffers we can test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to use a Monster object for this test because it has many fields of all different types (including nested structs) in order to exercise all of the possibilities. However, in this particular test file (test_cpp27.cpp) there is not an existing function that creates a Monster object like this, so I have added this one. If there is another one somewhere else that you are aware of that I can reuse (which has all the different field types represented and populated) let me know and I am happy to reuse it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CreateMonsterDirect + GetBuffer (or GetBufferSpan) + flatbuffers::GetRoot<Monster>?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I started changing to this approach (CreateMonsterDirect) but that seems a bit inconvenient in this case because it will force me to put many parameters corresponding to fields that I am not using, since I am populating a few fields towards the end, I cannot rely on the default values and I would have many parameters just nullptr or 0. Perhaps in this case where we are just populating a few select fields the Builder approach is better?
|
@dpacbach
Need to adjust https://github.com/google/flatbuffers/blob/master/.github/workflows/build.yml file. Update: I've checked this code without |
| } | ||
|
|
||
| const Monster* BuildMonster(flatbuffers::FlatBufferBuilder& fbb) { | ||
| using ::cpp17::MyGame::Example::MonsterBuilder; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CreateMonsterDirect + GetBuffer (or GetBufferSpan) + flatbuffers::GetRoot<Monster>?
|
@vglavnyy Thanks for helping me to fix the MSVC issues, I will make those changes that you suggest in your commit. But I am not clear on something that you said:
I did not see the call stack in your commit, how do I view it? But actually, I think there is a minor bug in your code at the end of the text += "]";
return text;
}I believe that should be Regarding the MSVC 19 C++17 build in the CI... do you want me to enable C++17 in that |
|
Comit 7575a2c (without changes)
Probably no one CI execute cpp17 tests.
For this PR fix of build.yml for The current status of the |
|
@vglavnyy update:
|
|
Ah, wasn't aware that moving VS2017/19 to GitHub Actions removed the C++17 testing, sorry! |
|
@aardappel We are ready for your review, PTAL when you have a chance. Just wanted to point out though that the broken CI check above appears to be coming from another change in master, I don't quite understand what it means. Apart from that, all the checks look good. |
aardappel
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This LGTM.
I wonder, since it generates a fair bit of code and it is somewhat niche in use, wether it should be behind a flag. After all, we have less niche uses of optional things behind a flag, e.g. --gen-object-api and --gen-mutable etc. It's not the biggest deal, but I know we have people relying on FlatBuffers being small in code size.
src/idl_gen_cpp.cpp
Outdated
|
|
||
| if (opts_.gen_nullable) { code_ += "#pragma clang system_header\n\n"; } | ||
|
|
||
| if (opts_.g_cpp_std >= cpp::CPP_STD_17) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this instead go into base.h or similar?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'd probably avoid that, because the <tuple> header is a bit long and template heavy, and so we probably don't want to force users to include it unless they have this C++ static reflection feature enabled (with C++17).
|
@aardappel I'm fine to put it behind a flag, but I will let you guys decide since it won't affect me either way (I will always have that flag on). If you decide that you want a flag, let me know what you want to call it (I suggest perhaps |
|
@vglavnyy what do you think? One thing that I would point out is that the majority of the C++17 features that we've added thus far (and the ones that I have an idea to add in the future, such as static reflection for enums which is now lacking) have to do with static reflection. So if we put those behind a flag, then the |
|
I think |
|
Yes, still think it would be good behind a flag. The use of C++17 is sure to expand in the future. |
|
@aardappel @vglavnyy Ok I have added the flag |
|
@aardappel I have addressed all of @vglavnyy 's suggestions and he has approved, and the CI checks look good as far as I can see (I think there is one failure there that is not related to my change), so now just waiting for your final approval and we can merge. |
|
Looks good, thanks! |
|
@dpacbach you forget to add |
|
This will break existing code that already depends on |
|
@vglavnyy Ok I will send a PR shortly to restore it back to the way it was. |
* [idl_parser] Add kTokenNumericConstant token
This commit adds the new token for correct parsing of signed numeric constants.
Before this expressions `-nan` or `-inf` were treated as kTokenStringConstant.
This was ambiguous if a real string field parsed.
For example, `{ "text_field" : -name }` was accepted by the parser as valid JSON object.
Related oss-fuzz issue: 6200301176619008
* Add additional positive tests fo 'inf' and 'nan' as identifiers
* Rebase to HEAD
* Move processing of signed constants to ParseSingleValue method.
* Add missed `--cpp-static-reflection` (#6324) to pass CI
* Remove `flatbuffers.pc` from repository to unblock CI (#6455).
Probably the generated flatbuffers.pc should not be a part of repo.
* Fix FieldIdentifierTest()
This PR adds compile-time static reflection facilities to Flatbuffers' Tables and Structs in C++17 mode. Specifically:
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 both compile time and runtime jointly as a form of static reflection. This includes field types, field names, and a generic field-getter approach based on a tuple.
A comprehensive unit test that also serves as an example. It demonstrates the power of this reflection by implementing a full recursive stringifier for arbitrary Flatbuffers types, but without needing any runtime access to the *.fbs definition files; the computation is done using only the static reflection facilities introduced in this commit and is fully generic.
Built in C++17 mode and ran
generate_code.sh, and included diffs in this commit. Only cpp17 generated files are affected.Tested on Linux with gcc 10.2.0, but should work on all compilers.
Fixes #6285