From dd288f71f316e73bf6a421721f861a760d5d4bfe Mon Sep 17 00:00:00 2001 From: Vladimir Glavnyy <31897320+vglavnyy@users.noreply.github.com> Date: Tue, 8 Jan 2019 01:04:44 +0700 Subject: [PATCH] Add `NaN` and `Inf` defaults to the C++ generated code. (#5102) * Add `NaN` and `Inf` defaults to the C++ generated code. * Refactoring: add FloatConstantGenerator * Refactoring-2: - remove isnan checking for all float/double values - add most probable implementation of virtual methods of FloatConstantGenerator * Add conditional (FLATBUFFERS_NAN_DEFAULTS) isnan checking --- appveyor.yml | 6 + include/flatbuffers/code_generators.h | 27 +++ include/flatbuffers/flatbuffers.h | 22 ++- src/code_generators.cpp | 31 ++++ src/idl_gen_cpp.cpp | 47 +++++- tests/generate_code.bat | 8 + tests/generate_code.sh | 1 + tests/monster_extra.fbs | 12 ++ tests/monster_extra_generated.h | 229 ++++++++++++++++++++++++++ tests/test.cpp | 1 + 10 files changed, 379 insertions(+), 5 deletions(-) create mode 100644 tests/monster_extra.fbs create mode 100644 tests/monster_extra_generated.h diff --git a/appveyor.yml b/appveyor.yml index f8e4e86c6..2cd22e92b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -13,8 +13,13 @@ environment: matrix: - CMAKE_VS_VERSION: "10 2010" + MONSTER_EXTRA: "skip" + - CMAKE_VS_VERSION: "12 2013" + MONSTER_EXTRA: "skip" + - CMAKE_VS_VERSION: "14 2015" + MONSTER_EXTRA: "" platform: - x86 @@ -25,6 +30,7 @@ configuration: - Release before_build: + - set MONSTER_EXTRA=%MONSTER_EXTRA% - cmake -G"Visual Studio %CMAKE_VS_VERSION%" # This cuts down on a lot of noise generated by xamarin warnings. - del "C:\Program Files (x86)\MSBuild\14.0\Microsoft.Common.targets\ImportAfter\Xamarin.Common.targets" diff --git a/include/flatbuffers/code_generators.h b/include/flatbuffers/code_generators.h index d19002e69..8077d3003 100644 --- a/include/flatbuffers/code_generators.h +++ b/include/flatbuffers/code_generators.h @@ -130,6 +130,33 @@ extern void GenComment(const std::vector &dc, std::string *code_ptr, const CommentConfig *config, const char *prefix = ""); +class FloatConstantGenerator { + public: + virtual ~FloatConstantGenerator(){}; + std::string GenFloatConstant(const FieldDef &field) const; + + private: + virtual std::string Inf(double v) const = 0; + virtual std::string NaN(double v) const = 0; + virtual std::string Value(double v, const std::string &src) const { + (void)v; + return src; + } + + virtual std::string Inf(float v) const { + return this->Inf(static_cast(v)); + } + virtual std::string NaN(float v) const { + return this->NaN(static_cast(v)); + } + virtual std::string Value(float v, const std::string &src) const { + return this->Value(static_cast(v), src); + } + + template + std::string GenFloatConstantImpl(const FieldDef &field) const; +}; + } // namespace flatbuffers #endif // FLATBUFFERS_CODE_GENERATORS_H_ diff --git a/include/flatbuffers/flatbuffers.h b/include/flatbuffers/flatbuffers.h index 2c4183441..cb796b0dd 100644 --- a/include/flatbuffers/flatbuffers.h +++ b/include/flatbuffers/flatbuffers.h @@ -19,7 +19,25 @@ #include "flatbuffers/base.h" +#if defined(FLATBUFFERS_NAN_DEFAULTS) +#include +#endif + namespace flatbuffers { +// Generic 'operator==' with conditional specialisations. +template inline bool IsTheSameAs(T e, T def) { return e == def; } + +#if defined(FLATBUFFERS_NAN_DEFAULTS) && \ + (!defined(_MSC_VER) || _MSC_VER >= 1800) +// Like `operator==(e, def)` with weak NaN if T=(float|double). +template<> inline bool IsTheSameAs(float e, float def) { + return (e == def) || (std::isnan(def) && std::isnan(e)); +} +template<> inline bool IsTheSameAs(double e, double def) { + return (e == def) || (std::isnan(def) && std::isnan(e)); +} +#endif + // Wrapper for uoffset_t to allow safe template specialization. // Value is allowed to be 0 to indicate a null object (see e.g. AddOffset). template struct Offset { @@ -1087,7 +1105,7 @@ class FlatBufferBuilder { // Like PushElement, but additionally tracks the field this represents. template void AddElement(voffset_t field, T e, T def) { // We don't serialize values equal to the default. - if (e == def && !force_defaults_) return; + if (IsTheSameAs(e, def) && !force_defaults_) return; auto off = PushElement(e); TrackField(field, off); } @@ -2241,7 +2259,7 @@ class Table { template bool SetField(voffset_t field, T val, T def) { auto field_offset = GetOptionalFieldOffset(field); - if (!field_offset) return val == def; + if (!field_offset) return IsTheSameAs(val, def); WriteScalar(data_ + field_offset, val); return true; } diff --git a/src/code_generators.cpp b/src/code_generators.cpp index 2ecd5e3a7..b78b5ca1d 100644 --- a/src/code_generators.cpp +++ b/src/code_generators.cpp @@ -19,6 +19,8 @@ #include "flatbuffers/base.h" #include "flatbuffers/util.h" +#include + #if defined(_MSC_VER) # pragma warning(push) # pragma warning(disable : 4127) // C4127: conditional expression is constant @@ -157,6 +159,35 @@ void GenComment(const std::vector &dc, std::string *code_ptr, } } +template +std::string FloatConstantGenerator::GenFloatConstantImpl( + const FieldDef &field) const { + const auto &constant = field.value.constant; + T v; + auto done = StringToNumber(constant.c_str(), &v); + FLATBUFFERS_ASSERT(done); + if (done) { +#if (!defined(_MSC_VER) || (_MSC_VER >= 1800)) + if (std::isnan(v)) return NaN(v); + if (std::isinf(v)) return Inf(v); +#endif + return Value(v, constant); + } + return "#"; // compile time error +} + +std::string FloatConstantGenerator::GenFloatConstant( + const FieldDef &field) const { + switch (field.value.type.base_type) { + case BASE_TYPE_FLOAT: return GenFloatConstantImpl(field); + case BASE_TYPE_DOUBLE: return GenFloatConstantImpl(field); + default: { + FLATBUFFERS_ASSERT(false); + return "INVALID_BASE_TYPE"; + } + }; +} + } // namespace flatbuffers #if defined(_MSC_VER) diff --git a/src/idl_gen_cpp.cpp b/src/idl_gen_cpp.cpp index f91f14e1f..06896d9c6 100644 --- a/src/idl_gen_cpp.cpp +++ b/src/idl_gen_cpp.cpp @@ -34,6 +34,44 @@ static std::string GeneratedFileName(const std::string &path, } namespace cpp { +class CppFloatConstantGenerator : public FloatConstantGenerator { + protected: + std::string Value(double v, + const std::string &src) const FLATBUFFERS_OVERRIDE { + (void)v; + return src; + }; + + std::string Value(float v, + const std::string &src) const FLATBUFFERS_OVERRIDE { + (void)v; + return src + "f"; + } + + std::string NaN(double v) const FLATBUFFERS_OVERRIDE { + (void)v; + return "std::numeric_limits::quiet_NaN()"; + } + std::string NaN(float v) const FLATBUFFERS_OVERRIDE { + (void)v; + return "std::numeric_limits::quiet_NaN()"; + } + + std::string Inf(double v) const FLATBUFFERS_OVERRIDE { + if(v < 0) + return "-std::numeric_limits::infinity()"; + else + return "std::numeric_limits::infinity()"; + } + + std::string Inf(float v) const FLATBUFFERS_OVERRIDE { + if (v < 0) + return "-std::numeric_limits::infinity()"; + else + return "std::numeric_limits::infinity()"; + } +}; + class CppGenerator : public BaseGenerator { public: CppGenerator(const Parser &parser, const std::string &path, @@ -1392,9 +1430,10 @@ class CppGenerator : public BaseGenerator { } std::string GenDefaultConstant(const FieldDef &field) { - return field.value.type.base_type == BASE_TYPE_FLOAT - ? field.value.constant + "f" - : field.value.constant; + if(IsFloat(field.value.type.base_type)) + return float_const_gen_.GenFloatConstant(field); + else + return field.value.constant; } std::string GetDefaultScalarValue(const FieldDef &field, bool is_ctor) { @@ -2745,6 +2784,8 @@ class CppGenerator : public BaseGenerator { cur_name_space_ = ns; } + + const CppFloatConstantGenerator float_const_gen_; }; } // namespace cpp diff --git a/tests/generate_code.bat b/tests/generate_code.bat index 304fecec5..d2e836428 100644 --- a/tests/generate_code.bat +++ b/tests/generate_code.bat @@ -20,6 +20,14 @@ if "%1"=="-b" set buildtype=%2 ..\%buildtype%\flatc.exe --cpp --js --ts --php --gen-mutable --reflect-names --gen-object-api --gen-compare --cpp-ptr-type flatbuffers::unique_ptr -o union_vector ./union_vector/union_vector.fbs || goto FAIL ..\%buildtype%\flatc.exe -b --schema --bfbs-comments --bfbs-builtins -I include_test monster_test.fbs || goto FAIL ..\%buildtype%\flatc.exe --jsonschema --schema -I include_test monster_test.fbs || goto FAIL + +IF NOT "%MONSTER_EXTRA%"=="skip" ( + @echo Generate MosterExtra + ..\%buildtype%\flatc.exe --cpp --gen-mutable --reflect-names --gen-object-api --gen-compare --no-includes monster_extra.fbs || goto FAIL +) else ( + @echo monster_extra.fbs skipped (the strtod function from MSVC2013 or older doesn't support NaN/Inf arguments) +) + cd ../samples ..\%buildtype%\flatc.exe --cpp --lobster --gen-mutable --reflect-names --gen-object-api --gen-compare --cpp-ptr-type flatbuffers::unique_ptr monster.fbs || goto FAIL ..\%buildtype%\flatc.exe -b --schema --bfbs-comments --bfbs-builtins monster.fbs || goto FAIL diff --git a/tests/generate_code.sh b/tests/generate_code.sh index d5ade21a8..6a3a8f854 100755 --- a/tests/generate_code.sh +++ b/tests/generate_code.sh @@ -20,6 +20,7 @@ set -e ../flatc --cpp --js --ts --php --gen-mutable --reflect-names --gen-object-api --gen-compare --cpp-ptr-type flatbuffers::unique_ptr -o union_vector ./union_vector/union_vector.fbs ../flatc -b --schema --bfbs-comments --bfbs-builtins -I include_test monster_test.fbs ../flatc --jsonschema --schema -I include_test monster_test.fbs +../flatc --cpp --gen-mutable --reflect-names --gen-object-api --gen-compare --no-includes monster_extra.fbs || goto FAIL cd ../samples ../flatc --cpp --lobster --gen-mutable --reflect-names --gen-object-api --gen-compare --cpp-ptr-type flatbuffers::unique_ptr monster.fbs ../flatc -b --schema --bfbs-comments --bfbs-builtins monster.fbs diff --git a/tests/monster_extra.fbs b/tests/monster_extra.fbs new file mode 100644 index 000000000..2518f5b17 --- /dev/null +++ b/tests/monster_extra.fbs @@ -0,0 +1,12 @@ +namespace MyGame; + +// Not all programmining languages support this extra table. +table MonsterExra { + // Float-point values with NaN and Inf defaults. + testf_nan:float = nan; + testf_pinf:float = +inf; + testf_ninf:float = -inf; + testd_nan:double = nan; + testd_pinf:double = +inf; + testd_ninf:double = -inf; +} diff --git a/tests/monster_extra_generated.h b/tests/monster_extra_generated.h new file mode 100644 index 000000000..0dad3b057 --- /dev/null +++ b/tests/monster_extra_generated.h @@ -0,0 +1,229 @@ +// automatically generated by the FlatBuffers compiler, do not modify + + +#ifndef FLATBUFFERS_GENERATED_MONSTEREXTRA_MYGAME_H_ +#define FLATBUFFERS_GENERATED_MONSTEREXTRA_MYGAME_H_ + +#include "flatbuffers/flatbuffers.h" + +namespace MyGame { + +struct MonsterExra; +struct MonsterExraT; + +bool operator==(const MonsterExraT &lhs, const MonsterExraT &rhs); + +inline const flatbuffers::TypeTable *MonsterExraTypeTable(); + +struct MonsterExraT : public flatbuffers::NativeTable { + typedef MonsterExra TableType; + float testf_nan; + float testf_pinf; + float testf_ninf; + double testd_nan; + double testd_pinf; + double testd_ninf; + MonsterExraT() + : testf_nan(std::numeric_limits::quiet_NaN()), + testf_pinf(std::numeric_limits::infinity()), + testf_ninf(-std::numeric_limits::infinity()), + testd_nan(std::numeric_limits::quiet_NaN()), + testd_pinf(std::numeric_limits::infinity()), + testd_ninf(-std::numeric_limits::infinity()) { + } +}; + +inline bool operator==(const MonsterExraT &lhs, const MonsterExraT &rhs) { + return + (lhs.testf_nan == rhs.testf_nan) && + (lhs.testf_pinf == rhs.testf_pinf) && + (lhs.testf_ninf == rhs.testf_ninf) && + (lhs.testd_nan == rhs.testd_nan) && + (lhs.testd_pinf == rhs.testd_pinf) && + (lhs.testd_ninf == rhs.testd_ninf); +} + +struct MonsterExra FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { + typedef MonsterExraT NativeTableType; + static const flatbuffers::TypeTable *MiniReflectTypeTable() { + return MonsterExraTypeTable(); + } + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_TESTF_NAN = 4, + VT_TESTF_PINF = 6, + VT_TESTF_NINF = 8, + VT_TESTD_NAN = 10, + VT_TESTD_PINF = 12, + VT_TESTD_NINF = 14 + }; + float testf_nan() const { + return GetField(VT_TESTF_NAN, std::numeric_limits::quiet_NaN()); + } + bool mutate_testf_nan(float _testf_nan) { + return SetField(VT_TESTF_NAN, _testf_nan, std::numeric_limits::quiet_NaN()); + } + float testf_pinf() const { + return GetField(VT_TESTF_PINF, std::numeric_limits::infinity()); + } + bool mutate_testf_pinf(float _testf_pinf) { + return SetField(VT_TESTF_PINF, _testf_pinf, std::numeric_limits::infinity()); + } + float testf_ninf() const { + return GetField(VT_TESTF_NINF, -std::numeric_limits::infinity()); + } + bool mutate_testf_ninf(float _testf_ninf) { + return SetField(VT_TESTF_NINF, _testf_ninf, -std::numeric_limits::infinity()); + } + double testd_nan() const { + return GetField(VT_TESTD_NAN, std::numeric_limits::quiet_NaN()); + } + bool mutate_testd_nan(double _testd_nan) { + return SetField(VT_TESTD_NAN, _testd_nan, std::numeric_limits::quiet_NaN()); + } + double testd_pinf() const { + return GetField(VT_TESTD_PINF, std::numeric_limits::infinity()); + } + bool mutate_testd_pinf(double _testd_pinf) { + return SetField(VT_TESTD_PINF, _testd_pinf, std::numeric_limits::infinity()); + } + double testd_ninf() const { + return GetField(VT_TESTD_NINF, -std::numeric_limits::infinity()); + } + bool mutate_testd_ninf(double _testd_ninf) { + return SetField(VT_TESTD_NINF, _testd_ninf, -std::numeric_limits::infinity()); + } + bool Verify(flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_TESTF_NAN) && + VerifyField(verifier, VT_TESTF_PINF) && + VerifyField(verifier, VT_TESTF_NINF) && + VerifyField(verifier, VT_TESTD_NAN) && + VerifyField(verifier, VT_TESTD_PINF) && + VerifyField(verifier, VT_TESTD_NINF) && + verifier.EndTable(); + } + MonsterExraT *UnPack(const flatbuffers::resolver_function_t *_resolver = nullptr) const; + void UnPackTo(MonsterExraT *_o, const flatbuffers::resolver_function_t *_resolver = nullptr) const; + static flatbuffers::Offset Pack(flatbuffers::FlatBufferBuilder &_fbb, const MonsterExraT* _o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); +}; + +struct MonsterExraBuilder { + flatbuffers::FlatBufferBuilder &fbb_; + flatbuffers::uoffset_t start_; + void add_testf_nan(float testf_nan) { + fbb_.AddElement(MonsterExra::VT_TESTF_NAN, testf_nan, std::numeric_limits::quiet_NaN()); + } + void add_testf_pinf(float testf_pinf) { + fbb_.AddElement(MonsterExra::VT_TESTF_PINF, testf_pinf, std::numeric_limits::infinity()); + } + void add_testf_ninf(float testf_ninf) { + fbb_.AddElement(MonsterExra::VT_TESTF_NINF, testf_ninf, -std::numeric_limits::infinity()); + } + void add_testd_nan(double testd_nan) { + fbb_.AddElement(MonsterExra::VT_TESTD_NAN, testd_nan, std::numeric_limits::quiet_NaN()); + } + void add_testd_pinf(double testd_pinf) { + fbb_.AddElement(MonsterExra::VT_TESTD_PINF, testd_pinf, std::numeric_limits::infinity()); + } + void add_testd_ninf(double testd_ninf) { + fbb_.AddElement(MonsterExra::VT_TESTD_NINF, testd_ninf, -std::numeric_limits::infinity()); + } + explicit MonsterExraBuilder(flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + MonsterExraBuilder &operator=(const MonsterExraBuilder &); + flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = flatbuffers::Offset(end); + return o; + } +}; + +inline flatbuffers::Offset CreateMonsterExra( + flatbuffers::FlatBufferBuilder &_fbb, + float testf_nan = std::numeric_limits::quiet_NaN(), + float testf_pinf = std::numeric_limits::infinity(), + float testf_ninf = -std::numeric_limits::infinity(), + double testd_nan = std::numeric_limits::quiet_NaN(), + double testd_pinf = std::numeric_limits::infinity(), + double testd_ninf = -std::numeric_limits::infinity()) { + MonsterExraBuilder builder_(_fbb); + builder_.add_testd_ninf(testd_ninf); + builder_.add_testd_pinf(testd_pinf); + builder_.add_testd_nan(testd_nan); + builder_.add_testf_ninf(testf_ninf); + builder_.add_testf_pinf(testf_pinf); + builder_.add_testf_nan(testf_nan); + return builder_.Finish(); +} + +flatbuffers::Offset CreateMonsterExra(flatbuffers::FlatBufferBuilder &_fbb, const MonsterExraT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); + +inline MonsterExraT *MonsterExra::UnPack(const flatbuffers::resolver_function_t *_resolver) const { + auto _o = new MonsterExraT(); + UnPackTo(_o, _resolver); + return _o; +} + +inline void MonsterExra::UnPackTo(MonsterExraT *_o, const flatbuffers::resolver_function_t *_resolver) const { + (void)_o; + (void)_resolver; + { auto _e = testf_nan(); _o->testf_nan = _e; }; + { auto _e = testf_pinf(); _o->testf_pinf = _e; }; + { auto _e = testf_ninf(); _o->testf_ninf = _e; }; + { auto _e = testd_nan(); _o->testd_nan = _e; }; + { auto _e = testd_pinf(); _o->testd_pinf = _e; }; + { auto _e = testd_ninf(); _o->testd_ninf = _e; }; +} + +inline flatbuffers::Offset MonsterExra::Pack(flatbuffers::FlatBufferBuilder &_fbb, const MonsterExraT* _o, const flatbuffers::rehasher_function_t *_rehasher) { + return CreateMonsterExra(_fbb, _o, _rehasher); +} + +inline flatbuffers::Offset CreateMonsterExra(flatbuffers::FlatBufferBuilder &_fbb, const MonsterExraT *_o, const flatbuffers::rehasher_function_t *_rehasher) { + (void)_rehasher; + (void)_o; + struct _VectorArgs { flatbuffers::FlatBufferBuilder *__fbb; const MonsterExraT* __o; const flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; + auto _testf_nan = _o->testf_nan; + auto _testf_pinf = _o->testf_pinf; + auto _testf_ninf = _o->testf_ninf; + auto _testd_nan = _o->testd_nan; + auto _testd_pinf = _o->testd_pinf; + auto _testd_ninf = _o->testd_ninf; + return MyGame::CreateMonsterExra( + _fbb, + _testf_nan, + _testf_pinf, + _testf_ninf, + _testd_nan, + _testd_pinf, + _testd_ninf); +} + +inline const flatbuffers::TypeTable *MonsterExraTypeTable() { + static const flatbuffers::TypeCode type_codes[] = { + { flatbuffers::ET_FLOAT, 0, -1 }, + { flatbuffers::ET_FLOAT, 0, -1 }, + { flatbuffers::ET_FLOAT, 0, -1 }, + { flatbuffers::ET_DOUBLE, 0, -1 }, + { flatbuffers::ET_DOUBLE, 0, -1 }, + { flatbuffers::ET_DOUBLE, 0, -1 } + }; + static const char * const names[] = { + "testf_nan", + "testf_pinf", + "testf_ninf", + "testd_nan", + "testd_pinf", + "testd_ninf" + }; + static const flatbuffers::TypeTable tt = { + flatbuffers::ST_TABLE, 6, type_codes, nullptr, nullptr, names + }; + return &tt; +} + +} // namespace MyGame + +#endif // FLATBUFFERS_GENERATED_MONSTEREXTRA_MYGAME_H_ diff --git a/tests/test.cpp b/tests/test.cpp index 656849a49..76d3efe3c 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -33,6 +33,7 @@ #include "namespace_test/namespace_test1_generated.h" #include "namespace_test/namespace_test2_generated.h" #include "union_vector/union_vector_generated.h" +#include "monster_extra_generated.h" #include "test_assert.h" #include "flatbuffers/flexbuffers.h"