diff --git a/CMakeLists.txt b/CMakeLists.txt index 7bf48e6ae..b0990af60 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -238,6 +238,9 @@ set(FlatBuffers_Tests_SRCS tests/util_test.cpp tests/vector_table_naked_ptr_test.h tests/vector_table_naked_ptr_test.cpp + tests/test_vector_type.h + tests/cpp_vector_type_test.h + tests/cpp_vector_type_test.cpp tests/native_type_test_impl.h tests/native_type_test_impl.cpp tests/alignment_test.h @@ -548,6 +551,7 @@ if(FLATBUFFERS_BUILD_TESTS) # The flattest target needs some generated files SET(FLATC_OPT_COMP --cpp --gen-compare --gen-mutable --gen-object-api --reflect-names) SET(FLATC_OPT_SCOPED_ENUMS ${FLATC_OPT_COMP};--scoped-enums) + SET(FLATC_OPT_CPP_VECTOR_TYPE ${FLATC_OPT_COMP};--cpp-include;test_vector_type.h;--cpp-vector-type;::flatbuffers::tests::CustomVector) compile_schema_for_test(tests/alignment_test.fbs "${FLATC_OPT_COMP}") compile_schema_for_test_fbsh(tests/default_vectors_strings_test.fbs "${FLATC_OPT_COMP}") @@ -559,6 +563,7 @@ if(FLATBUFFERS_BUILD_TESTS) compile_schema_for_test(tests/64bit/evolution/v1.fbs "${FLATC_OPT_COMP}") compile_schema_for_test(tests/64bit/evolution/v2.fbs "${FLATC_OPT_COMP}") compile_schema_for_test(tests/union_underlying_type_test.fbs "${FLATC_OPT_SCOPED_ENUMS}") + compile_schema_for_test(tests/cpp_vector_type.fbs "${FLATC_OPT_CPP_VECTOR_TYPE}") if(FLATBUFFERS_CODE_SANITIZE) add_fsanitize_to_target(flattests ${FLATBUFFERS_CODE_SANITIZE}) diff --git a/docs/source/flatc.md b/docs/source/flatc.md index 598992e22..150b3bd83 100644 --- a/docs/source/flatc.md +++ b/docs/source/flatc.md @@ -168,6 +168,12 @@ list of `FILES...`. std::string from Flatbuffers, but (char* + length). This allows efficient construction of custom string types, including zero-copy construction. +- `--cpp-vector-type T` : Set object API vector type (default std::vector). + T must be a template taking a single element type argument and support + resize(), reserve(), size(), data(), operator[], emplace_back() and + begin()/end(), matching the subset of std::vector's interface generated + code relies on. + - `--no-cpp-direct-copy` : Don't generate direct copy methods for C++ object-based API. diff --git a/docs/source/languages/cpp.md b/docs/source/languages/cpp.md index 445ca7301..a1769717d 100644 --- a/docs/source/languages/cpp.md +++ b/docs/source/languages/cpp.md @@ -337,6 +337,27 @@ constructor in the following format: `custom_str_class(const char *, size_t)`. Please note that the character array is not guaranteed to be NULL terminated, you should always use the provided size to determine end of string. +## Using different vector type + +By default the object tree's vector fields are built out of `std::vector`, +but you can influence this either globally (using the `--cpp-vector-type` +argument to `flatc`) or per field using the `cpp_vector_type` attribute, to +use any other vector-like template type (e.g. `eastl::vector`). + +The type must be a template taking a single element type argument +(`my_vector`), and must support the following member functions: +`resize()`, `reserve()`, `size()`, `data()`, `operator[]`, `emplace_back()`, +and `begin()`/`end()`. This matches the subset of `std::vector`'s interface +that generated code relies on. + +Note that unlike `std::vector`, the custom vector type must not use a +bit-packed specialization for `bool` elements, since generated code accesses +`data()` as a contiguous `bool` array. + +As with custom string types, the header defining the custom vector type is +not automatically included; use `--cpp-include` to add the necessary +`#include`. + ## Reflection (& Resizing) There is experimental support for reflection in FlatBuffers, allowing you to diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h index 9e58d4be9..4a1126683 100644 --- a/include/flatbuffers/idl.h +++ b/include/flatbuffers/idl.h @@ -677,6 +677,7 @@ struct IDLOptions { std::string cpp_object_api_pointer_type; std::string cpp_object_api_string_type; bool cpp_object_api_string_flexible_constructor; + std::string cpp_object_api_vector_type; CaseStyle cpp_object_api_field_case_style; bool cpp_direct_copy; bool gen_nullable; @@ -1012,6 +1013,7 @@ class Parser : public ParserState { known_attributes_["cpp_ptr_type_get"] = true; known_attributes_["cpp_str_type"] = true; known_attributes_["cpp_str_flex_ctor"] = true; + known_attributes_["cpp_vector_type"] = true; known_attributes_["native_inline"] = true; known_attributes_["native_custom_alloc"] = true; known_attributes_["native_type"] = true; diff --git a/scripts/generate_code.py b/scripts/generate_code.py index 713b3109e..c06026c73 100755 --- a/scripts/generate_code.py +++ b/scripts/generate_code.py @@ -251,6 +251,21 @@ flatc( schema="vector_table_naked_ptr.fbs", ) +flatc( + [ + "--cpp", + "--gen-compare", + "--gen-mutable", + "--gen-object-api", + "--reflect-names", + "--cpp-include", + "test_vector_type.h", + "--cpp-vector-type", + "::flatbuffers::tests::CustomVector", + ], + schema="cpp_vector_type.fbs", +) + flatc( BASE_OPTS + CPP_OPTS + CS_OPTS + JAVA_OPTS + KOTLIN_OPTS + PHP_OPTS, prefix="union_vector", diff --git a/src/flatc.cpp b/src/flatc.cpp index ee34044fd..5dd306441 100644 --- a/src/flatc.cpp +++ b/src/flatc.cpp @@ -145,6 +145,12 @@ const static FlatCOption flatc_options[] = { {"", "cpp-str-flex-ctor", "", "Don't construct custom string types by passing std::string from " "Flatbuffers, but (char* + length)."}, + {"", "cpp-vector-type", "T", + "Set object API vector type (default std::vector). T must be a " + "template taking a single element type argument and support " + "resize(), reserve(), size(), data(), operator[], emplace_back(), and " + "begin()/end(). The custom type also needs its own header to be " + "included via --cpp-include."}, {"", "cpp-field-case-style", "STYLE", "Generate C++ fields using selected case style. Supported STYLE values: * " "'unchanged' - leave unchanged (default) * 'upper' - schema snake_case " @@ -545,6 +551,9 @@ FlatCOptions FlatCompiler::ParseFromCommandLineArguments(int argc, opts.cpp_object_api_string_type = argv[argi]; } else if (arg == "--cpp-str-flex-ctor") { opts.cpp_object_api_string_flexible_constructor = true; + } else if (arg == "--cpp-vector-type") { + if (++argi >= argc) Error("missing type following: " + arg, true); + opts.cpp_object_api_vector_type = argv[argi]; } else if (arg == "--no-cpp-direct-copy") { opts.cpp_direct_copy = false; } else if (arg == "--cpp-field-case-style") { diff --git a/src/idl_gen_cpp.cpp b/src/idl_gen_cpp.cpp index 51bb82049..6fc820c06 100644 --- a/src/idl_gen_cpp.cpp +++ b/src/idl_gen_cpp.cpp @@ -937,6 +937,23 @@ class CppGenerator : public BaseGenerator { "std::string"; // Only for custom string types. } + const std::string NativeVectorType(const FieldDef* field) { + auto attr = field ? field->attributes.Lookup("cpp_vector_type") : nullptr; + auto& ret = attr ? attr->constant : opts_.cpp_object_api_vector_type; + if (ret.empty()) { + return "std::vector"; + } + return ret; + } + + // True if this field's native vector uses a custom (non-std::vector) + // container, meaning generated Pack() code can't rely on the std::vector + // specific CreateVector*() overloads and must fall back to data()/size() + // or per-element construction instead. + bool UsesCustomVectorType(const FieldDef* field) { + return NativeVectorType(field) != "std::vector"; + } + std::string GenTypeNativePtr(const std::string& type, const FieldDef* field, bool is_constructor) { auto& ptr_type = PtrType(field); @@ -974,14 +991,15 @@ class CppGenerator : public BaseGenerator { case BASE_TYPE_VECTOR64: case BASE_TYPE_VECTOR: { const auto type_name = GenTypeNative(type.VectorType(), true, field); - if (type.struct_def && + const auto vector_type = NativeVectorType(&field); + if (vector_type == "std::vector" && type.struct_def && type.struct_def->attributes.Lookup("native_custom_alloc")) { auto native_custom_alloc = type.struct_def->attributes.Lookup("native_custom_alloc"); return "std::vector<" + type_name + "," + native_custom_alloc->constant + "<" + type_name + ">>"; } else { - return "std::vector<" + type_name + ">"; + return vector_type + "<" + type_name + ">"; } } case BASE_TYPE_STRUCT: { @@ -1979,7 +1997,7 @@ class CppGenerator : public BaseGenerator { const std::string& full_type = (cpp_type ? (IsVector(field.value.type) - ? "std::vector<" + + ? NativeVectorType(&field) + "<" + GenTypeNativePtr(cpp_type->constant, &field, false) + "> " @@ -3772,13 +3790,22 @@ class CppGenerator : public BaseGenerator { case BASE_TYPE_VECTOR64: case BASE_TYPE_VECTOR: { auto vector_type = field.value.type.VectorType(); + // If the field's native container isn't std::vector, the + // std::vector-specific CreateVector*() overloads in + // flatbuffer_builder.h can't be used directly. Fall back to + // data()/size()-based overloads (which only require the container to + // support those, like std::vector does) or, where no such overload + // exists, to the same per-element lambda serialization already used + // above for custom string/table/union element types. + const bool custom_vector = UsesCustomVectorType(&field); switch (vector_type.base_type) { case BASE_TYPE_STRING: { - if (NativeString(&field) == "std::string") { + if (!custom_vector && NativeString(&field) == "std::string") { code += "_fbb.CreateVectorOfStrings(" + value + ")"; } else { // Use by-function serialization to emulate - // CreateVectorOfStrings(); this works also with non-std strings. + // CreateVectorOfStrings(); this works also with non-std strings + // and non-std::vector vector types. code += "_fbb.CreateVector<::flatbuffers::Offset<::flatbuffers::" "String>>" @@ -3800,13 +3827,31 @@ class CppGenerator : public BaseGenerator { code += "_fbb.CreateVectorOfNativeStructs<"; code += WrapInNameSpace(*vector_type.struct_def) + ", " + native_type->constant + ">"; - code += "(" + value; + if (custom_vector) { + code += "(" + value + ".data(), " + value + ".size()"; + } else { + code += "(" + value; + } const auto pack_name = struct_attrs.Lookup("native_type_pack_name"); if (pack_name) { code += ", ::flatbuffers::Pack" + pack_name->constant; } code += ")"; + } else if (custom_vector && + (field.value.type.base_type == BASE_TYPE_VECTOR64 || + field.offset64)) { + // CreateVectorOfStructs64() (and CreateVectorOfStructs64() + // for offset64) only accept a std::vector; use the + // equivalent explicit-template raw pointer overload instead. + const auto struct_type = WrapInNameSpace(*vector_type.struct_def); + const auto vector_t = field.value.type.base_type == + BASE_TYPE_VECTOR64 + ? "::flatbuffers::Vector64" + : "::flatbuffers::Vector"; + code += "_fbb.CreateVectorOfStructs<" + struct_type + + ", ::flatbuffers::Offset64, " + vector_t + ">(" + + value + ".data(), " + value + ".size())"; } else { // If the field uses 64-bit addressing, create a 64-bit vector. if (field.value.type.base_type == BASE_TYPE_VECTOR64) { @@ -3818,7 +3863,11 @@ class CppGenerator : public BaseGenerator { code += "64<::flatbuffers::Vector>"; } } - code += "(" + value + ")"; + if (custom_vector) { + code += "(" + value + ".data(), " + value + ".size())"; + } else { + code += "(" + value + ")"; + } } } else { code += "_fbb.CreateVector<::flatbuffers::Offset<"; @@ -3837,7 +3886,17 @@ class CppGenerator : public BaseGenerator { break; } case BASE_TYPE_BOOL: { - code += "_fbb.CreateVector(" + value + ")"; + if (custom_vector) { + // Vectors of bool are always stored on the wire as uint8_t + // (there is no Vector); CreateVectorScalarCast() does + // the per-element bool->uint8_t cast from a raw data()/size() + // pointer pair, avoiding any dependency on std::vector's + // bit-packed specialization. + code += "_fbb.CreateVectorScalarCast(" + value + + ".data(), " + value + ".size())"; + } else { + code += "_fbb.CreateVector(" + value + ")"; + } break; } case BASE_TYPE_UNION: { @@ -3873,9 +3932,11 @@ class CppGenerator : public BaseGenerator { // the underlying storage type (eg. uint8_t). const auto basetype = GenTypeBasic( field.value.type.enum_def->underlying_type, false); - code += "_fbb.CreateVectorScalarCast<" + basetype + - ">(::flatbuffers::data(" + value + "), " + value + - ".size())"; + const std::string data_ptr = + custom_vector ? value + ".data()" + : "::flatbuffers::data(" + value + ")"; + code += "_fbb.CreateVectorScalarCast<" + basetype + ">(" + + data_ptr + ", " + value + ".size())"; } else if (field.attributes.Lookup("cpp_type")) { auto type = GenTypeBasic(vector_type, false); code += "_fbb.CreateVector<" + type + ">(" + value + ".size(), "; @@ -3884,6 +3945,27 @@ class CppGenerator : public BaseGenerator { code += "static_cast<" + type + ">((*__va->__rehasher)"; code += "(__va->_" + value + "[i]" + GenPtrGet(field) + ")) : 0"; code += "; }, &_va )"; + } else if (custom_vector) { + // Explicitly specify the element type so it can be deduced + // from a raw data()/size() pointer pair instead of relying on + // the std::vector-specific CreateVector overloads. + // Use the user-facing type (matches what GenTypeNative() + // stores in the native vector, e.g. the enum type itself for + // scoped-enum fields) so it matches data()'s pointee type. + auto type = GenTypeBasic(vector_type, true); + if (field.value.type.base_type == BASE_TYPE_VECTOR64) { + code += "_fbb.CreateVector<" + type + + ", ::flatbuffers::Offset64, ::flatbuffers::Vector64>(" + + value + ".data(), " + value + ".size())"; + } else if (field.offset64) { + // This is normal 32-bit vector, with 64-bit addressing. + code += "_fbb.CreateVector<" + type + + ", ::flatbuffers::Offset64, ::flatbuffers::Vector>(" + + value + ".data(), " + value + ".size())"; + } else { + code += "_fbb.CreateVector(" + value + ".data(), " + value + + ".size())"; + } } else { // If the field uses 64-bit addressing, create a 64-bit vector. if (field.value.type.base_type == BASE_TYPE_VECTOR64) { diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 6a7373aaa..5451da940 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -78,6 +78,9 @@ cc_test( "vector_table_naked_ptr/vector_table_naked_ptr_generated.h", "vector_table_naked_ptr_test.h", "vector_table_naked_ptr_test.cpp", + "test_vector_type.h", + "cpp_vector_type_test.h", + "cpp_vector_type_test.cpp", ], copts = [ "-DFLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE", @@ -136,6 +139,7 @@ cc_test( deps = [ ":alignment_test_cc_fbs", ":arrays_test_cc_fbs", + ":cpp_vector_type_cc_fbs", ":default_vectors_strings_test_cc_fbs", ":monster_extra_cc_fbs", ":monster_test_cc_fbs", @@ -271,6 +275,19 @@ flatbuffer_cc_library( ], ) +flatbuffer_cc_library( + name = "cpp_vector_type_cc_fbs", + srcs = ["cpp_vector_type.fbs"], + flatc_args = [ + "--gen-compare", + "--gen-mutable", + "--gen-object-api", + "--reflect-names", + "--cpp-include test_vector_type.h", + "--cpp-vector-type ::flatbuffers::tests::CustomVector", + ], +) + flatbuffer_cc_library( name = "alignment_test_cc_fbs", srcs = ["alignment_test.fbs"], diff --git a/tests/cpp_vector_type.fbs b/tests/cpp_vector_type.fbs new file mode 100644 index 000000000..4a5b502c3 --- /dev/null +++ b/tests/cpp_vector_type.fbs @@ -0,0 +1,29 @@ +// Schema used to test the --cpp-vector-type flatc option, which lets the +// generated Object API use a custom vector-like container (e.g. +// eastl::vector) instead of std::vector. See tests/test_vector_type.h for +// the stand-in container used by the test, and +// tests/cpp_vector_type_test.cpp for the test itself. + +namespace CppVectorTypeNS; + +enum CppVectorTypeColor : byte { Red = 0, Green, Blue } + +struct CppVectorTypeVec2 { + x: float; + y: float; +} + +table CppVectorTypeMonster { + id: int32; +} + +table CppVectorTypeTest { + ints: [int32]; + flags: [bool]; + colors: [CppVectorTypeColor]; + strings: [string]; + positions: [CppVectorTypeVec2]; + monsters: [CppVectorTypeMonster]; +} + +root_type CppVectorTypeTest; diff --git a/tests/cpp_vector_type_test.cpp b/tests/cpp_vector_type_test.cpp new file mode 100644 index 000000000..fd7bde616 --- /dev/null +++ b/tests/cpp_vector_type_test.cpp @@ -0,0 +1,143 @@ +#include "cpp_vector_type_test.h" + +#include "cpp_vector_type_generated.h" +#include "test_assert.h" + +namespace flatbuffers { +namespace tests { + +// Exercises the --cpp-vector-type flatc option (see +// tests/cpp_vector_type.fbs and tests/test_vector_type.h), which replaces +// std::vector with a custom vector-like container in the generated Object +// API. This covers every CreateVector*-family code path in Pack(): plain +// scalars, bool, enum-cast, vector-of-string, vector-of-struct and +// vector-of-table, then round-trips everything back through UnPackTo(). +void CppVectorTypeTest() { + using CppVectorTypeNS::CppVectorTypeColor_Blue; + using CppVectorTypeNS::CppVectorTypeColor_Green; + using CppVectorTypeNS::CppVectorTypeColor_Red; + using CppVectorTypeNS::CppVectorTypeMonsterT; + using CppVectorTypeNS::CppVectorTypeTest; + using CppVectorTypeNS::CppVectorTypeTestT; + using CppVectorTypeNS::CppVectorTypeVec2; + + // --------------------------------------- + // 1) Build a native object using the custom vector container for every + // vector field (scalars, bools, enums, strings, structs and tables). + // --------------------------------------- + CppVectorTypeTestT src; + src.ints.push_back(1); + src.ints.push_back(2); + src.ints.push_back(3); + + src.flags.push_back(true); + src.flags.push_back(false); + src.flags.push_back(true); + + src.colors.push_back(CppVectorTypeColor_Red); + src.colors.push_back(CppVectorTypeColor_Green); + src.colors.push_back(CppVectorTypeColor_Blue); + + src.strings.push_back("hello"); + src.strings.push_back("world"); + + src.positions.push_back(CppVectorTypeVec2(1.0f, 2.0f)); + src.positions.push_back(CppVectorTypeVec2(3.0f, 4.0f)); + + src.monsters.emplace_back(new CppVectorTypeMonsterT()); + src.monsters[0]->id = 111; + src.monsters.emplace_back(new CppVectorTypeMonsterT()); + src.monsters[1]->id = 222; + + // --------------------------------------- + // 2) Exercise the copy constructor / copy assignment operator generated + // for object-API types (--gen-compare requires these too), which rely + // on the custom vector's own copy ctor plus reserve()/emplace_back() + // for the vector-of-pointer (table) field. + // --------------------------------------- + CppVectorTypeTestT copy_of_src(src); + TEST_EQ(copy_of_src == src, true); + TEST_ASSERT(copy_of_src.monsters[0].get() != src.monsters[0].get()); + TEST_EQ(copy_of_src.monsters[0]->id, 111); + + // --------------------------------------- + // 3) Pack into a FlatBuffer and verify the wire-format contents. + // --------------------------------------- + flatbuffers::FlatBufferBuilder fbb; + fbb.Finish(CppVectorTypeTest::Pack(fbb, &src)); + + const auto* fb = + flatbuffers::GetRoot(fbb.GetBufferPointer()); + + TEST_EQ(fb->ints()->size(), 3u); + TEST_EQ(fb->ints()->Get(0), 1); + TEST_EQ(fb->ints()->Get(1), 2); + TEST_EQ(fb->ints()->Get(2), 3); + + TEST_EQ(fb->flags()->size(), 3u); + TEST_EQ(fb->flags()->Get(0) != 0, true); + TEST_EQ(fb->flags()->Get(1) != 0, false); + TEST_EQ(fb->flags()->Get(2) != 0, true); + + TEST_EQ(fb->colors()->size(), 3u); + TEST_EQ(fb->colors()->Get(0), CppVectorTypeColor_Red); + TEST_EQ(fb->colors()->Get(1), CppVectorTypeColor_Green); + TEST_EQ(fb->colors()->Get(2), CppVectorTypeColor_Blue); + + TEST_EQ(fb->strings()->size(), 2u); + TEST_EQ_STR(fb->strings()->Get(0)->c_str(), "hello"); + TEST_EQ_STR(fb->strings()->Get(1)->c_str(), "world"); + + TEST_EQ(fb->positions()->size(), 2u); + TEST_EQ(fb->positions()->Get(0)->x(), 1.0f); + TEST_EQ(fb->positions()->Get(0)->y(), 2.0f); + TEST_EQ(fb->positions()->Get(1)->x(), 3.0f); + TEST_EQ(fb->positions()->Get(1)->y(), 4.0f); + + TEST_EQ(fb->monsters()->size(), 2u); + TEST_EQ(fb->monsters()->Get(0)->id(), 111); + TEST_EQ(fb->monsters()->Get(1)->id(), 222); + + // --------------------------------------- + // 4) Unpack back into a fresh native object and verify a full round-trip + // through the custom vector container. + // --------------------------------------- + CppVectorTypeTestT dst; + fb->UnPackTo(&dst); + + TEST_EQ(dst == src, true); + + TEST_EQ(dst.ints.size(), 3u); + TEST_EQ(dst.ints[0], 1); + TEST_EQ(dst.ints[1], 2); + TEST_EQ(dst.ints[2], 3); + + TEST_EQ(dst.flags.size(), 3u); + TEST_EQ(dst.flags[0], true); + TEST_EQ(dst.flags[1], false); + TEST_EQ(dst.flags[2], true); + + TEST_EQ(dst.colors.size(), 3u); + TEST_EQ(dst.colors[0], CppVectorTypeColor_Red); + TEST_EQ(dst.colors[1], CppVectorTypeColor_Green); + TEST_EQ(dst.colors[2], CppVectorTypeColor_Blue); + + TEST_EQ(dst.strings.size(), 2u); + TEST_EQ_STR(dst.strings[0].c_str(), "hello"); + TEST_EQ_STR(dst.strings[1].c_str(), "world"); + + TEST_EQ(dst.positions.size(), 2u); + TEST_EQ(dst.positions[0].x(), 1.0f); + TEST_EQ(dst.positions[0].y(), 2.0f); + TEST_EQ(dst.positions[1].x(), 3.0f); + TEST_EQ(dst.positions[1].y(), 4.0f); + + TEST_EQ(dst.monsters.size(), 2u); + TEST_ASSERT(dst.monsters[0] != nullptr); + TEST_ASSERT(dst.monsters[1] != nullptr); + TEST_EQ(dst.monsters[0]->id, 111); + TEST_EQ(dst.monsters[1]->id, 222); +} + +} // namespace tests +} // namespace flatbuffers diff --git a/tests/cpp_vector_type_test.h b/tests/cpp_vector_type_test.h new file mode 100644 index 000000000..e5bcc8f06 --- /dev/null +++ b/tests/cpp_vector_type_test.h @@ -0,0 +1,12 @@ +#ifndef TESTS_CPP_VECTOR_TYPE_TEST_H +#define TESTS_CPP_VECTOR_TYPE_TEST_H + +namespace flatbuffers { +namespace tests { + +void CppVectorTypeTest(); + +} // namespace tests +} // namespace flatbuffers + +#endif // TESTS_CPP_VECTOR_TYPE_TEST_H diff --git a/tests/test.cpp b/tests/test.cpp index c94d21592..4cf2e9381 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -66,6 +66,7 @@ #include "native_type_test_generated.h" #include "test_assert.h" #include "util_test.h" +#include "cpp_vector_type_test.h" #include "vector_table_naked_ptr_test.h" void FlatBufferBuilderTest(); @@ -1744,6 +1745,8 @@ int FlatBufferTests(const std::string& tests_data_path) { AlignmentTest(); + CppVectorTypeTest(); + #ifndef FLATBUFFERS_NO_FILE_TESTS ParseAndGenerateTextTest(tests_data_path, false); ParseAndGenerateTextTest(tests_data_path, true); diff --git a/tests/test_vector_type.h b/tests/test_vector_type.h new file mode 100644 index 000000000..9a40d5dfc --- /dev/null +++ b/tests/test_vector_type.h @@ -0,0 +1,152 @@ +#ifndef TESTS_TEST_VECTOR_TYPE_H_ +#define TESTS_TEST_VECTOR_TYPE_H_ + +#include +#include +#include + +namespace flatbuffers { +namespace tests { + +// A minimal stand-in for a custom vector-like container (e.g. +// eastl::vector), used to exercise the --cpp-vector-type flatc option. +// +// This is deliberately its own contiguous container rather than a wrapper +// around std::vector, so that: +// - generated code can't silently keep depending on any of the +// std::vector-specific overloads in flatbuffer_builder.h (e.g. +// CreateVector(const std::vector&)), and +// - CustomVector stores plain contiguous bools accessible via +// data(), unlike std::vector's bit-packed specialization, matching +// how real alternative containers such as eastl::vector behave. +template +class CustomVector { + public: + using value_type = T; + using iterator = T*; + using const_iterator = const T*; + + CustomVector() = default; + CustomVector(const CustomVector& o) { assign_copy(o); } + CustomVector(CustomVector&& o) noexcept { steal(o); } + + CustomVector& operator=(const CustomVector& o) { + if (this != &o) { + destroy_all(); + deallocate(); + assign_copy(o); + } + return *this; + } + + CustomVector& operator=(CustomVector&& o) noexcept { + if (this != &o) { + destroy_all(); + deallocate(); + steal(o); + } + return *this; + } + + ~CustomVector() { + destroy_all(); + deallocate(); + } + + void reserve(size_t n) { + if (n > capacity_) grow_to(n); + } + + void resize(size_t n) { + if (n > capacity_) grow_to(n); + for (size_t i = n; i < size_; ++i) data_[i].~T(); + for (size_t i = size_; i < n; ++i) new (&data_[i]) T(); + size_ = n; + } + + size_t size() const { return size_; } + bool empty() const { return size_ == 0; } + + T* data() { return data_; } + const T* data() const { return data_; } + + T& operator[](size_t i) { return data_[i]; } + const T& operator[](size_t i) const { return data_[i]; } + + template + void emplace_back(Args&&... args) { + if (size_ == capacity_) grow_to(capacity_ == 0 ? 1 : capacity_ * 2); + new (&data_[size_]) T(std::forward(args)...); + ++size_; + } + void push_back(const T& v) { emplace_back(v); } + + iterator begin() { return data_; } + iterator end() { return data_ + size_; } + const_iterator begin() const { return data_; } + const_iterator end() const { return data_ + size_; } + const_iterator cbegin() const { return data_; } + const_iterator cend() const { return data_ + size_; } + + private: + void grow_to(size_t n) { + T* new_data = static_cast(::operator new(n * sizeof(T))); + for (size_t i = 0; i < size_; ++i) { + new (&new_data[i]) T(std::move(data_[i])); + data_[i].~T(); + } + ::operator delete(data_); + data_ = new_data; + capacity_ = n; + } + + void assign_copy(const CustomVector& o) { + data_ = o.size_ ? static_cast(::operator new(o.size_ * sizeof(T))) + : nullptr; + capacity_ = o.size_; + for (size_t i = 0; i < o.size_; ++i) new (&data_[i]) T(o.data_[i]); + size_ = o.size_; + } + + void steal(CustomVector& o) { + data_ = o.data_; + size_ = o.size_; + capacity_ = o.capacity_; + o.data_ = nullptr; + o.size_ = o.capacity_ = 0; + } + + void destroy_all() { + for (size_t i = 0; i < size_; ++i) data_[i].~T(); + size_ = 0; + } + + void deallocate() { + ::operator delete(data_); + data_ = nullptr; + capacity_ = 0; + } + + T* data_ = nullptr; + size_t size_ = 0; + size_t capacity_ = 0; +}; + +template +bool operator==(const CustomVector& lhs, const CustomVector& rhs) { + if (lhs.size() != rhs.size()) return false; + for (size_t i = 0; i < lhs.size(); ++i) { + if (!(lhs[i] == rhs[i])) return false; + } + return true; +} + +template +bool operator!=(const CustomVector& lhs, const CustomVector& rhs) { + return !(lhs == rhs); +} + +} // namespace tests +} // namespace flatbuffers + +#endif // TESTS_TEST_VECTOR_TYPE_H_