diff --git a/include/flatbuffers/flatbuffers.h b/include/flatbuffers/flatbuffers.h index 2537d7e29..c073f3b05 100644 --- a/include/flatbuffers/flatbuffers.h +++ b/include/flatbuffers/flatbuffers.h @@ -259,6 +259,12 @@ template class Vector { typedef VectorReverseIterator reverse_iterator; typedef VectorReverseIterator const_reverse_iterator; + typedef typename flatbuffers::bool_constant::value> + scalar_tag; + + static FLATBUFFERS_CONSTEXPR bool is_span_observable = + scalar_tag::value && (FLATBUFFERS_LITTLEENDIAN || sizeof(T) == 1); + uoffset_t size() const { return EndianScalar(length_); } // Deprecated: use size(). Here for backwards compatibility. @@ -394,6 +400,38 @@ template class Vector { } }; +template +FLATBUFFERS_CONSTEXPR_CPP11 flatbuffers::span make_span(Vector &vec) + FLATBUFFERS_NOEXCEPT { + static_assert(Vector::is_span_observable, + "wrong type U, only LE-scalar, or byte types are allowed"); + return span(vec.data(), vec.size()); +} + +template +FLATBUFFERS_CONSTEXPR_CPP11 flatbuffers::span make_span( + const Vector &vec) FLATBUFFERS_NOEXCEPT { + static_assert(Vector::is_span_observable, + "wrong type U, only LE-scalar, or byte types are allowed"); + return span(vec.data(), vec.size()); +} + +template +FLATBUFFERS_CONSTEXPR_CPP11 flatbuffers::span make_bytes_span( + Vector &vec) FLATBUFFERS_NOEXCEPT { + static_assert(Vector::scalar_tag::value, + "wrong type U, only LE-scalar, or byte types are allowed"); + return span(vec.Data(), vec.size() * sizeof(U)); +} + +template +FLATBUFFERS_CONSTEXPR_CPP11 flatbuffers::span make_bytes_span( + const Vector &vec) FLATBUFFERS_NOEXCEPT { + static_assert(Vector::scalar_tag::value, + "wrong type U, only LE-scalar, or byte types are allowed"); + return span(vec.Data(), vec.size() * sizeof(U)); +} + // Represent a vector much like the template above, but in this case we // don't know what the element types are (used with reflection.h). class VectorOfAny { @@ -437,10 +475,9 @@ template static inline size_t VectorLength(const Vector *v) { // This is used as a helper type for accessing arrays. template class Array { - typedef - typename flatbuffers::integral_constant::value> - scalar_tag; + // Array can carry only POD data types (scalars or structs). + typedef typename flatbuffers::bool_constant::value> + scalar_tag; typedef typename flatbuffers::conditional::type IndirectHelperType; @@ -451,6 +488,11 @@ template class Array { typedef VectorIterator const_iterator; typedef VectorReverseIterator const_reverse_iterator; + // If T is a LE-scalar or a struct (!scalar_tag::value). + static FLATBUFFERS_CONSTEXPR bool is_span_observable = + (scalar_tag::value && (FLATBUFFERS_LITTLEENDIAN || sizeof(T) == 1)) || + !scalar_tag::value; + FLATBUFFERS_CONSTEXPR uint16_t size() const { return length; } return_type Get(uoffset_t i) const { @@ -515,26 +557,20 @@ template class Array { !(p2 >= p1 && p2 < (p1 + length))); (void)p1; (void)p2; - - CopyFromSpanImpl( - flatbuffers::integral_constant < bool, - !scalar_tag::value || sizeof(T) == 1 || FLATBUFFERS_LITTLEENDIAN > (), - src); + CopyFromSpanImpl(flatbuffers::bool_constant(), src); } protected: - void MutateImpl(flatbuffers::integral_constant, uoffset_t i, - const T &val) { + void MutateImpl(flatbuffers::true_type, uoffset_t i, const T &val) { FLATBUFFERS_ASSERT(i < size()); WriteScalar(data() + i, val); } - void MutateImpl(flatbuffers::integral_constant, uoffset_t i, - const T &val) { + void MutateImpl(flatbuffers::false_type, uoffset_t i, const T &val) { *(GetMutablePointer(i)) = val; } - void CopyFromSpanImpl(flatbuffers::integral_constant, + void CopyFromSpanImpl(flatbuffers::true_type, flatbuffers::span src) { // Use std::memcpy() instead of std::copy() to avoid preformance degradation // due to aliasing if T is char or unsigned char. @@ -543,7 +579,7 @@ template class Array { } // Copy data from flatbuffers::span with endian conversion. - void CopyFromSpanImpl(flatbuffers::integral_constant, + void CopyFromSpanImpl(flatbuffers::false_type, flatbuffers::span src) { for (size_type k = 0; k < length; k++) { Mutate(k, src[k]); } } @@ -593,8 +629,43 @@ template class Array, length> { uint8_t data_[1]; }; +template +FLATBUFFERS_CONSTEXPR_CPP11 flatbuffers::span make_span(Array &arr) + FLATBUFFERS_NOEXCEPT { + static_assert( + Array::is_span_observable, + "wrong type U, only plain struct, LE-scalar, or byte types are allowed"); + return span(arr.data(), N); +} + +template +FLATBUFFERS_CONSTEXPR_CPP11 flatbuffers::span make_span( + const Array &arr) FLATBUFFERS_NOEXCEPT { + static_assert( + Array::is_span_observable, + "wrong type U, only plain struct, LE-scalar, or byte types are allowed"); + return span(arr.data(), N); +} + +template +FLATBUFFERS_CONSTEXPR_CPP11 flatbuffers::span +make_bytes_span(Array &arr) FLATBUFFERS_NOEXCEPT { + static_assert(Array::is_span_observable, + "internal error, Array might hold only scalars or structs"); + return span(arr.Data(), sizeof(U) * N); +} + +template +FLATBUFFERS_CONSTEXPR_CPP11 flatbuffers::span +make_bytes_span(const Array &arr) FLATBUFFERS_NOEXCEPT { + static_assert(Array::is_span_observable, + "internal error, Array might hold only scalars or structs"); + return span(arr.Data(), sizeof(U) * N); +} + // Cast a raw T[length] to a raw flatbuffers::Array // without endian conversion. Use with care. +// TODO: move these Cast-methods to `internal` namespace. template Array &CastToArray(T (&arr)[length]) { return *reinterpret_cast *>(arr); diff --git a/include/flatbuffers/stl_emulation.h b/include/flatbuffers/stl_emulation.h index 70e5dc944..2697a0a8a 100644 --- a/include/flatbuffers/stl_emulation.h +++ b/include/flatbuffers/stl_emulation.h @@ -166,6 +166,8 @@ inline void vector_emplace_back(std::vector *vector, V &&data) { using integral_constant = std::integral_constant; template using bool_constant = integral_constant; + using true_type = std::true_type; + using false_type = std::false_type; #else // Map C++ TR1 templates defined by stlport. template using is_scalar = std::tr1::is_scalar; @@ -191,6 +193,8 @@ inline void vector_emplace_back(std::vector *vector, V &&data) { using integral_constant = std::tr1::integral_constant; template using bool_constant = integral_constant; + using true_type = bool_constant; + using false_type = bool_constant; #endif // !FLATBUFFERS_CPP98_STL #else // MSVC 2010 doesn't support C++11 aliases. @@ -207,6 +211,8 @@ inline void vector_emplace_back(std::vector *vector, V &&data) { struct integral_constant : public std::integral_constant {}; template struct bool_constant : public integral_constant {}; + typedef bool_constant true_type; + typedef bool_constant false_type; #endif // defined(FLATBUFFERS_TEMPLATES_ALIASES) #ifndef FLATBUFFERS_CPP98_STL @@ -495,6 +501,32 @@ namespace internal { int, void>::type; }; + template + struct SpanIterator { + // TODO: upgrade to std::random_access_iterator_tag. + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = typename std::remove_cv::type; + using reference = T&; + using pointer = T*; + + // Convince MSVC compiler that this iterator is trusted (it is verified). + #ifdef _MSC_VER + using _Unchecked_type = pointer; + #endif // _MSC_VER + + SpanIterator(pointer ptr) : ptr_(ptr) {} + reference operator*() const { return *ptr_; } + pointer operator->() { return ptr_; } + SpanIterator& operator++() { ptr_++; return *this; } + SpanIterator operator++(int) { auto tmp = *this; ++(*this); return tmp; } + + friend bool operator== (const SpanIterator& lhs, const SpanIterator& rhs) { return lhs.ptr_ == rhs.ptr_; } + friend bool operator!= (const SpanIterator& lhs, const SpanIterator& rhs) { return lhs.ptr_ != rhs.ptr_; } + + private: + pointer ptr_; + }; } // namespace internal #endif // !defined(FLATBUFFERS_SPAN_MINIMAL) @@ -534,6 +566,17 @@ class span FLATBUFFERS_FINAL_CLASS { return data_; } + #if !defined(FLATBUFFERS_SPAN_MINIMAL) + using Iterator = internal::SpanIterator; + using ConstIterator = internal::SpanIterator; + + Iterator begin() const { return Iterator(data()); } + Iterator end() const { return Iterator(data() + size()); } + + ConstIterator cbegin() const { return ConstIterator(data()); } + ConstIterator cend() const { return ConstIterator(data() + size()); } + #endif + // Returns a reference to the idx-th element of the sequence. // The behavior is undefined if the idx is greater than or equal to size(). FLATBUFFERS_CONSTEXPR_CPP11 reference operator[](size_type idx) const { @@ -627,47 +670,46 @@ class span FLATBUFFERS_FINAL_CLASS { pointer const data_; const size_type count_; }; - - #if !defined(FLATBUFFERS_SPAN_MINIMAL) - template - FLATBUFFERS_CONSTEXPR_CPP11 - flatbuffers::span make_span(U(&arr)[N]) FLATBUFFERS_NOEXCEPT { - return span(arr); - } - - template - FLATBUFFERS_CONSTEXPR_CPP11 - flatbuffers::span make_span(const U(&arr)[N]) FLATBUFFERS_NOEXCEPT { - return span(arr); - } - - template - FLATBUFFERS_CONSTEXPR_CPP11 - flatbuffers::span make_span(std::array &arr) FLATBUFFERS_NOEXCEPT { - return span(arr); - } - - template - FLATBUFFERS_CONSTEXPR_CPP11 - flatbuffers::span make_span(const std::array &arr) FLATBUFFERS_NOEXCEPT { - return span(arr); - } - - template - FLATBUFFERS_CONSTEXPR_CPP11 - flatbuffers::span make_span(U *first, std::size_t count) FLATBUFFERS_NOEXCEPT { - return span(first, count); - } - - template - FLATBUFFERS_CONSTEXPR_CPP11 - flatbuffers::span make_span(const U *first, std::size_t count) FLATBUFFERS_NOEXCEPT { - return span(first, count); - } -#endif - #endif // defined(FLATBUFFERS_USE_STD_SPAN) +#if !defined(FLATBUFFERS_SPAN_MINIMAL) +template +FLATBUFFERS_CONSTEXPR_CPP11 +flatbuffers::span make_span(U(&arr)[N]) FLATBUFFERS_NOEXCEPT { + return span(arr); +} + +template +FLATBUFFERS_CONSTEXPR_CPP11 +flatbuffers::span make_span(const U(&arr)[N]) FLATBUFFERS_NOEXCEPT { + return span(arr); +} + +template +FLATBUFFERS_CONSTEXPR_CPP11 +flatbuffers::span make_span(std::array &arr) FLATBUFFERS_NOEXCEPT { + return span(arr); +} + +template +FLATBUFFERS_CONSTEXPR_CPP11 +flatbuffers::span make_span(const std::array &arr) FLATBUFFERS_NOEXCEPT { + return span(arr); +} + +template +FLATBUFFERS_CONSTEXPR_CPP11 +flatbuffers::span make_span(U *first, std::size_t count) FLATBUFFERS_NOEXCEPT { + return span(first, count); +} + +template +FLATBUFFERS_CONSTEXPR_CPP11 +flatbuffers::span make_span(const U *first, std::size_t count) FLATBUFFERS_NOEXCEPT { + return span(first, count); +} +#endif // !defined(FLATBUFFERS_SPAN_MINIMAL) + } // namespace flatbuffers #endif // FLATBUFFERS_STL_EMULATION_H_ diff --git a/tests/test.cpp b/tests/test.cpp index 6576a10e2..7ae8a2b1e 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -3499,9 +3499,9 @@ void FlatbuffersSpanTest() { void FlatbuffersSpanTest() {} #endif -void FixedLengthArrayTest() { - // VS10 does not support typed enums, exclude from tests +// VS10 does not support typed enums, exclude from tests #if !defined(_MSC_VER) || _MSC_VER >= 1700 +void FixedLengthArrayTest() { // Generate an ArrayTable containing one ArrayStruct. flatbuffers::FlatBufferBuilder fbb; MyGame::Example::NestedStruct nStruct0(MyGame::Example::TestEnum::B); @@ -3535,10 +3535,10 @@ void FixedLengthArrayTest() { aStruct.mutable_d()->Mutate(1, nStruct1); auto aTable = MyGame::Example::CreateArrayTable(fbb, &aStruct); MyGame::Example::FinishArrayTableBuffer(fbb, aTable); - // Verify correctness of the ArrayTable. flatbuffers::Verifier verifier(fbb.GetBufferPointer(), fbb.GetSize()); - MyGame::Example::VerifyArrayTableBuffer(verifier); + TEST_ASSERT(MyGame::Example::VerifyArrayTableBuffer(verifier)); + // Do test. auto p = MyGame::Example::GetMutableArrayTable(fbb.GetBufferPointer()); auto mArStruct = p->mutable_a(); TEST_NOTNULL(mArStruct); @@ -3548,10 +3548,10 @@ void FixedLengthArrayTest() { TEST_NOTNULL(mArStruct->mutable_b()); TEST_NOTNULL(mArStruct->mutable_d()); TEST_NOTNULL(mArStruct->mutable_f()); - mArStruct->mutable_b()->Mutate(14, -14); TEST_EQ(mArStruct->a(), 2); TEST_EQ(mArStruct->b()->size(), 15); - TEST_EQ(mArStruct->b()->Get(aStruct.b()->size() - 1), -14); + mArStruct->mutable_b()->Mutate(14, -14); + TEST_EQ(mArStruct->b()->Get(14), -14); TEST_EQ(mArStruct->c(), 12); TEST_NOTNULL(mArStruct->d()->Get(0)); TEST_NOTNULL(mArStruct->d()->Get(0)->a()); @@ -3603,8 +3603,10 @@ void FixedLengthArrayTest() { # endif (void)ap; for (size_t i = 0; i < arr_size; ++i) { TEST_EQ(non_zero_memory[i], 0); } -#endif } +#else +void FixedLengthArrayTest() {} +#endif // !defined(_MSC_VER) || _MSC_VER >= 1700 #if !defined(FLATBUFFERS_SPAN_MINIMAL) && \ (!defined(_MSC_VER) || _MSC_VER >= 1700) @@ -3689,9 +3691,9 @@ void NativeTypeTest() { } } -void FixedLengthArrayJsonTest(bool binary) { - // VS10 does not support typed enums, exclude from tests +// VS10 does not support typed enums, exclude from tests #if !defined(_MSC_VER) || _MSC_VER >= 1700 +void FixedLengthArrayJsonTest(bool binary) { // load FlatBuffer schema (.fbs) and JSON from disk std::string schemafile; std::string jsonfile; @@ -3748,11 +3750,86 @@ void FixedLengthArrayJsonTest(bool binary) { parserGen.builder_.GetBufferPointer(), parserOrg.builder_.GetSize()), 0); -#else - (void)binary; -#endif } +void FixedLengthArraySpanTest() { + // load FlatBuffer schema (.fbs) and JSON from disk + std::string schemafile; + std::string jsonfile; + TEST_EQ(flatbuffers::LoadFile((test_data_path + "arrays_test.fbs").c_str(), + false, &schemafile), + true); + TEST_EQ(flatbuffers::LoadFile((test_data_path + "arrays_test.golden").c_str(), + false, &jsonfile), + true); + + // parse schema first, so we can use it to parse the data after + flatbuffers::Parser parser; + TEST_EQ(parser.Parse(schemafile.c_str()), true); + TEST_EQ(parser.Parse(jsonfile.c_str()), true); + auto &fbb = parser.builder_; + auto verifier = flatbuffers::Verifier(fbb.GetBufferPointer(), fbb.GetSize()); + TEST_EQ(true, VerifyArrayTableBuffer(verifier)); + + auto p = MyGame::Example::GetMutableArrayTable(fbb.GetBufferPointer()); + TEST_NOTNULL(p); + auto table_struct = p->mutable_a(); + TEST_NOTNULL(table_struct); + TEST_EQ(2, table_struct->d()->size()); + TEST_NOTNULL(table_struct->d()); + TEST_NOTNULL(table_struct->mutable_d()); + // test array of structs + auto const_d = flatbuffers::make_span(*table_struct->d()); + auto mutable_d = flatbuffers::make_span(*table_struct->mutable_d()); + TEST_EQ(2, const_d.size()); + TEST_EQ(2, mutable_d.size()); + TEST_ASSERT(const_d[0] == mutable_d[0]); + TEST_ASSERT(const_d[1] == mutable_d[1]); + mutable_d[0] = const_d[0]; // mutate + // test scalars + auto &const_nested = const_d[0]; + auto &mutable_nested = mutable_d[0]; + static_assert(sizeof(MyGame::Example::TestEnum) == sizeof(uint8_t), + "TestEnum's underlaying type must by byte"); + TEST_NOTNULL(const_nested.d()); + TEST_NOTNULL(mutable_nested.d()); + { + flatbuffers::span const_d_c = + flatbuffers::make_span(*const_nested.c()); + auto mutable_d_c = flatbuffers::make_span(*mutable_nested.mutable_c()); + TEST_EQ(2, const_d_c.size()); + TEST_EQ(2, mutable_d_c.size()); + TEST_EQ(MyGame::Example::TestEnum::C, const_d_c[0]); + TEST_EQ(MyGame::Example::TestEnum::B, const_d_c[1]); + TEST_ASSERT(mutable_d_c.end() == std::copy(const_d_c.cbegin(), + const_d_c.cend(), + mutable_d_c.begin())); + TEST_ASSERT( + std::equal(const_d_c.cbegin(), const_d_c.cend(), mutable_d_c.cbegin())); + } + // test little endian array of int32 +# if FLATBUFFERS_LITTLEENDIAN + { + flatbuffers::span const_d_a = + flatbuffers::make_span(*const_nested.a()); + auto mutable_d_a = flatbuffers::make_span(*mutable_nested.mutable_a()); + TEST_EQ(2, const_d_a.size()); + TEST_EQ(2, mutable_d_a.size()); + TEST_EQ(-1, const_d_a[0]); + TEST_EQ(2, const_d_a[1]); + TEST_ASSERT(mutable_d_a.end() == std::copy(const_d_a.cbegin(), + const_d_a.cend(), + mutable_d_a.begin())); + TEST_ASSERT( + std::equal(const_d_a.cbegin(), const_d_a.cend(), mutable_d_a.cbegin())); + } +# endif +} +#else +void FixedLengthArrayJsonTest(bool /*binary*/) {} +void FixedLengthArraySpanTest() {} +#endif + void TestEmbeddedBinarySchema() { // load JSON from disk std::string jsonfile; @@ -4147,6 +4224,7 @@ int FlatBufferTests() { ParseIncorrectMonsterJsonTest(); FlexBuffersFloatingPointTest(); FlatbuffersIteratorsTest(); + FixedLengthArraySpanTest(); return 0; }