[C++] add make_span for Array<T,N> (#6663)

* [C++] Add make_span for Array<T,N> and Vector<T>

* [C++] Add tests for make_span<T,N>(Array<T,N>)

* [C++] Add span iterators

* [C++] Checked span iterator (MSVC)

* Fix review notes
This commit is contained in:
Vladimir Glavnyy 2021-06-29 00:22:02 +07:00 committed by GitHub
parent bd37e67ac0
commit 22498cf3a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 257 additions and 66 deletions

View File

@ -259,6 +259,12 @@ template<typename T> class Vector {
typedef VectorReverseIterator<iterator> reverse_iterator;
typedef VectorReverseIterator<const_iterator> const_reverse_iterator;
typedef typename flatbuffers::bool_constant<flatbuffers::is_scalar<T>::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<typename T> class Vector {
}
};
template<class U>
FLATBUFFERS_CONSTEXPR_CPP11 flatbuffers::span<U> make_span(Vector<U> &vec)
FLATBUFFERS_NOEXCEPT {
static_assert(Vector<U>::is_span_observable,
"wrong type U, only LE-scalar, or byte types are allowed");
return span<U>(vec.data(), vec.size());
}
template<class U>
FLATBUFFERS_CONSTEXPR_CPP11 flatbuffers::span<const U> make_span(
const Vector<U> &vec) FLATBUFFERS_NOEXCEPT {
static_assert(Vector<U>::is_span_observable,
"wrong type U, only LE-scalar, or byte types are allowed");
return span<const U>(vec.data(), vec.size());
}
template<class U>
FLATBUFFERS_CONSTEXPR_CPP11 flatbuffers::span<uint8_t> make_bytes_span(
Vector<U> &vec) FLATBUFFERS_NOEXCEPT {
static_assert(Vector<U>::scalar_tag::value,
"wrong type U, only LE-scalar, or byte types are allowed");
return span<uint8_t>(vec.Data(), vec.size() * sizeof(U));
}
template<class U>
FLATBUFFERS_CONSTEXPR_CPP11 flatbuffers::span<const uint8_t> make_bytes_span(
const Vector<U> &vec) FLATBUFFERS_NOEXCEPT {
static_assert(Vector<U>::scalar_tag::value,
"wrong type U, only LE-scalar, or byte types are allowed");
return span<const uint8_t>(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<typename T> static inline size_t VectorLength(const Vector<T> *v) {
// This is used as a helper type for accessing arrays.
template<typename T, uint16_t length> class Array {
typedef
typename flatbuffers::integral_constant<bool,
flatbuffers::is_scalar<T>::value>
scalar_tag;
// Array<T> can carry only POD data types (scalars or structs).
typedef typename flatbuffers::bool_constant<flatbuffers::is_scalar<T>::value>
scalar_tag;
typedef
typename flatbuffers::conditional<scalar_tag::value, T, const T *>::type
IndirectHelperType;
@ -451,6 +488,11 @@ template<typename T, uint16_t length> class Array {
typedef VectorIterator<T, return_type> const_iterator;
typedef VectorReverseIterator<const_iterator> 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<typename T, uint16_t length> 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<is_span_observable>(), src);
}
protected:
void MutateImpl(flatbuffers::integral_constant<bool, true>, 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<bool, false>, 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<bool, true>,
void CopyFromSpanImpl(flatbuffers::true_type,
flatbuffers::span<const T, length> 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<typename T, uint16_t length> class Array {
}
// Copy data from flatbuffers::span with endian conversion.
void CopyFromSpanImpl(flatbuffers::integral_constant<bool, false>,
void CopyFromSpanImpl(flatbuffers::false_type,
flatbuffers::span<const T, length> src) {
for (size_type k = 0; k < length; k++) { Mutate(k, src[k]); }
}
@ -593,8 +629,43 @@ template<typename T, uint16_t length> class Array<Offset<T>, length> {
uint8_t data_[1];
};
template<class U, uint16_t N>
FLATBUFFERS_CONSTEXPR_CPP11 flatbuffers::span<U, N> make_span(Array<U, N> &arr)
FLATBUFFERS_NOEXCEPT {
static_assert(
Array<U, N>::is_span_observable,
"wrong type U, only plain struct, LE-scalar, or byte types are allowed");
return span<U, N>(arr.data(), N);
}
template<class U, uint16_t N>
FLATBUFFERS_CONSTEXPR_CPP11 flatbuffers::span<const U, N> make_span(
const Array<U, N> &arr) FLATBUFFERS_NOEXCEPT {
static_assert(
Array<U, N>::is_span_observable,
"wrong type U, only plain struct, LE-scalar, or byte types are allowed");
return span<const U, N>(arr.data(), N);
}
template<class U, uint16_t N>
FLATBUFFERS_CONSTEXPR_CPP11 flatbuffers::span<uint8_t, sizeof(U) * N>
make_bytes_span(Array<U, N> &arr) FLATBUFFERS_NOEXCEPT {
static_assert(Array<U, N>::is_span_observable,
"internal error, Array<T> might hold only scalars or structs");
return span<uint8_t, sizeof(U) * N>(arr.Data(), sizeof(U) * N);
}
template<class U, uint16_t N>
FLATBUFFERS_CONSTEXPR_CPP11 flatbuffers::span<const uint8_t, sizeof(U) * N>
make_bytes_span(const Array<U, N> &arr) FLATBUFFERS_NOEXCEPT {
static_assert(Array<U, N>::is_span_observable,
"internal error, Array<T> might hold only scalars or structs");
return span<const uint8_t, sizeof(U) * N>(arr.Data(), sizeof(U) * N);
}
// Cast a raw T[length] to a raw flatbuffers::Array<T, length>
// without endian conversion. Use with care.
// TODO: move these Cast-methods to `internal` namespace.
template<typename T, uint16_t length>
Array<T, length> &CastToArray(T (&arr)[length]) {
return *reinterpret_cast<Array<T, length> *>(arr);

View File

@ -166,6 +166,8 @@ inline void vector_emplace_back(std::vector<T> *vector, V &&data) {
using integral_constant = std::integral_constant<T, v>;
template <bool B>
using bool_constant = integral_constant<bool, B>;
using true_type = std::true_type;
using false_type = std::false_type;
#else
// Map C++ TR1 templates defined by stlport.
template <typename T> using is_scalar = std::tr1::is_scalar<T>;
@ -191,6 +193,8 @@ inline void vector_emplace_back(std::vector<T> *vector, V &&data) {
using integral_constant = std::tr1::integral_constant<T, v>;
template <bool B>
using bool_constant = integral_constant<bool, B>;
using true_type = bool_constant<true>;
using false_type = bool_constant<false>;
#endif // !FLATBUFFERS_CPP98_STL
#else
// MSVC 2010 doesn't support C++11 aliases.
@ -207,6 +211,8 @@ inline void vector_emplace_back(std::vector<T> *vector, V &&data) {
struct integral_constant : public std::integral_constant<T, v> {};
template <bool B>
struct bool_constant : public integral_constant<bool, B> {};
typedef bool_constant<true> true_type;
typedef bool_constant<false> false_type;
#endif // defined(FLATBUFFERS_TEMPLATES_ALIASES)
#ifndef FLATBUFFERS_CPP98_STL
@ -495,6 +501,32 @@ namespace internal {
int, void>::type;
};
template<typename T>
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<T>::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<T>;
using ConstIterator = internal::SpanIterator<const T>;
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<class U, std::size_t N>
FLATBUFFERS_CONSTEXPR_CPP11
flatbuffers::span<U, N> make_span(U(&arr)[N]) FLATBUFFERS_NOEXCEPT {
return span<U, N>(arr);
}
template<class U, std::size_t N>
FLATBUFFERS_CONSTEXPR_CPP11
flatbuffers::span<const U, N> make_span(const U(&arr)[N]) FLATBUFFERS_NOEXCEPT {
return span<const U, N>(arr);
}
template<class U, std::size_t N>
FLATBUFFERS_CONSTEXPR_CPP11
flatbuffers::span<U, N> make_span(std::array<U, N> &arr) FLATBUFFERS_NOEXCEPT {
return span<U, N>(arr);
}
template<class U, std::size_t N>
FLATBUFFERS_CONSTEXPR_CPP11
flatbuffers::span<const U, N> make_span(const std::array<U, N> &arr) FLATBUFFERS_NOEXCEPT {
return span<const U, N>(arr);
}
template<class U, std::size_t N>
FLATBUFFERS_CONSTEXPR_CPP11
flatbuffers::span<U, dynamic_extent> make_span(U *first, std::size_t count) FLATBUFFERS_NOEXCEPT {
return span<U, dynamic_extent>(first, count);
}
template<class U, std::size_t N>
FLATBUFFERS_CONSTEXPR_CPP11
flatbuffers::span<const U, dynamic_extent> make_span(const U *first, std::size_t count) FLATBUFFERS_NOEXCEPT {
return span<const U, dynamic_extent>(first, count);
}
#endif
#endif // defined(FLATBUFFERS_USE_STD_SPAN)
#if !defined(FLATBUFFERS_SPAN_MINIMAL)
template<class U, std::size_t N>
FLATBUFFERS_CONSTEXPR_CPP11
flatbuffers::span<U, N> make_span(U(&arr)[N]) FLATBUFFERS_NOEXCEPT {
return span<U, N>(arr);
}
template<class U, std::size_t N>
FLATBUFFERS_CONSTEXPR_CPP11
flatbuffers::span<const U, N> make_span(const U(&arr)[N]) FLATBUFFERS_NOEXCEPT {
return span<const U, N>(arr);
}
template<class U, std::size_t N>
FLATBUFFERS_CONSTEXPR_CPP11
flatbuffers::span<U, N> make_span(std::array<U, N> &arr) FLATBUFFERS_NOEXCEPT {
return span<U, N>(arr);
}
template<class U, std::size_t N>
FLATBUFFERS_CONSTEXPR_CPP11
flatbuffers::span<const U, N> make_span(const std::array<U, N> &arr) FLATBUFFERS_NOEXCEPT {
return span<const U, N>(arr);
}
template<class U, std::size_t N>
FLATBUFFERS_CONSTEXPR_CPP11
flatbuffers::span<U, dynamic_extent> make_span(U *first, std::size_t count) FLATBUFFERS_NOEXCEPT {
return span<U, dynamic_extent>(first, count);
}
template<class U, std::size_t N>
FLATBUFFERS_CONSTEXPR_CPP11
flatbuffers::span<const U, dynamic_extent> make_span(const U *first, std::size_t count) FLATBUFFERS_NOEXCEPT {
return span<const U, dynamic_extent>(first, count);
}
#endif // !defined(FLATBUFFERS_SPAN_MINIMAL)
} // namespace flatbuffers
#endif // FLATBUFFERS_STL_EMULATION_H_

View File

@ -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 MyGame::Example::TestEnum, 2> 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 int32_t, 2> 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;
}