From 4638c9f23e9dfb67d4b8f41c79548352eb52d5b4 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 29 Mar 2026 14:04:31 +0200 Subject: [PATCH 01/42] Began developing cereal --- CMakeLists.txt | 42 ++++++-- README.md | 3 + conanfile.py | 5 + docs/supported_formats/cereal.md | 135 ++++++++++++++++++++++++++ include/rfl/cereal.hpp | 13 +++ include/rfl/cereal/Parser.hpp | 54 +++++++++++ include/rfl/cereal/Reader.hpp | 108 +++++++++++++++++++++ include/rfl/cereal/Writer.hpp | 107 ++++++++++++++++++++ include/rfl/cereal/load.hpp | 22 +++++ include/rfl/cereal/read.hpp | 61 ++++++++++++ include/rfl/cereal/save.hpp | 24 +++++ include/rfl/cereal/write.hpp | 47 +++++++++ mkdocs.yaml | 3 +- reflectcpp-config.cmake.in | 7 +- src/reflectcpp_cereal.cpp | 32 ++++++ tests/CMakeLists.txt | 4 + tests/cereal/CMakeLists.txt | 20 ++++ tests/cereal/test_array.cpp | 32 ++++++ tests/cereal/test_box.cpp | 43 ++++++++ tests/cereal/test_enum.cpp | 22 +++++ tests/cereal/test_map.cpp | 34 +++++++ tests/cereal/test_optional_fields.cpp | 24 +++++ tests/cereal/test_person.cpp | 31 ++++++ tests/cereal/test_readme_example.cpp | 46 +++++++++ tests/cereal/test_save_load.cpp | 30 ++++++ tests/cereal/test_tagged_union.cpp | 34 +++++++ tests/cereal/test_timestamp.cpp | 24 +++++ tests/cereal/test_tuple.cpp | 18 ++++ tests/cereal/test_unique_ptr.cpp | 44 +++++++++ tests/cereal/test_variant.cpp | 32 ++++++ tests/cereal/write_and_read.hpp | 18 ++++ vcpkg.json | 9 ++ 32 files changed, 1120 insertions(+), 8 deletions(-) create mode 100644 docs/supported_formats/cereal.md create mode 100644 include/rfl/cereal.hpp create mode 100644 include/rfl/cereal/Parser.hpp create mode 100644 include/rfl/cereal/Reader.hpp create mode 100644 include/rfl/cereal/Writer.hpp create mode 100644 include/rfl/cereal/load.hpp create mode 100644 include/rfl/cereal/read.hpp create mode 100644 include/rfl/cereal/save.hpp create mode 100644 include/rfl/cereal/write.hpp create mode 100644 src/reflectcpp_cereal.cpp create mode 100644 tests/cereal/CMakeLists.txt create mode 100644 tests/cereal/test_array.cpp create mode 100644 tests/cereal/test_box.cpp create mode 100644 tests/cereal/test_enum.cpp create mode 100644 tests/cereal/test_map.cpp create mode 100644 tests/cereal/test_optional_fields.cpp create mode 100644 tests/cereal/test_person.cpp create mode 100644 tests/cereal/test_readme_example.cpp create mode 100644 tests/cereal/test_save_load.cpp create mode 100644 tests/cereal/test_tagged_union.cpp create mode 100644 tests/cereal/test_timestamp.cpp create mode 100644 tests/cereal/test_tuple.cpp create mode 100644 tests/cereal/test_unique_ptr.cpp create mode 100644 tests/cereal/test_variant.cpp create mode 100644 tests/cereal/write_and_read.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a18a82ef2..4c8384205 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ option(REFLECTCPP_AVRO "Enable AVRO support" ${REFLECTCPP_ALL_FORMATS}) option(REFLECTCPP_BSON "Enable BSON support" ${REFLECTCPP_ALL_FORMATS}) option(REFLECTCPP_CAPNPROTO "Enable Cap’n Proto support" ${REFLECTCPP_ALL_FORMATS}) option(REFLECTCPP_CBOR "Enable CBOR support" ${REFLECTCPP_ALL_FORMATS}) +option(REFLECTCPP_CEREAL "Enable Cereal support" ${REFLECTCPP_ALL_FORMATS}) option(REFLECTCPP_CSV "Enable CSV support" ${REFLECTCPP_ALL_FORMATS}) option(REFLECTCPP_FLEXBUFFERS "Enable flexbuffers support" ${REFLECTCPP_ALL_FORMATS}) option(REFLECTCPP_MSGPACK "Enable msgpack support" ${REFLECTCPP_ALL_FORMATS}) @@ -49,6 +50,7 @@ if(REFLECTCPP_BUILD_BENCHMARKS) set(REFLECTCPP_BSON ON CACHE BOOL "" FORCE) set(REFLECTCPP_CAPNPROTO ON CACHE BOOL "" FORCE) set(REFLECTCPP_CBOR ON CACHE BOOL "" FORCE) + set(REFLECTCPP_CEREAL ON CACHE BOOL "" FORCE) set(REFLECTCPP_FLEXBUFFERS ON CACHE BOOL "" FORCE) set(REFLECTCPP_MSGPACK ON CACHE BOOL "" FORCE) set(REFLECTCPP_XML ON CACHE BOOL "" FORCE) @@ -57,11 +59,25 @@ if(REFLECTCPP_BUILD_BENCHMARKS) set(REFLECTCPP_YAML ON CACHE BOOL "" FORCE) endif() -if (REFLECTCPP_BUILD_TESTS OR REFLECTCPP_BUILD_BENCHMARKS OR REFLECTCPP_CHECK_HEADERS OR - (REFLECTCPP_JSON AND NOT REFLECTCPP_USE_BUNDLED_DEPENDENCIES) OR REFLECTCPP_AVRO OR - REFLECTCPP_BSON OR REFLECTCPP_CAPNPROTO OR REFLECTCPP_CBOR OR REFLECTCPP_CSV OR - REFLECTCPP_FLEXBUFFERS OR REFLECTCPP_MSGPACK OR REFLECTCPP_PARQUET OR REFLECTCPP_XML OR - REFLECTCPP_TOML OR REFLECTCPP_UBJSON OR REFLECTCPP_YAML) +if ( + REFLECTCPP_BUILD_TESTS OR + REFLECTCPP_BUILD_BENCHMARKS OR + REFLECTCPP_CHECK_HEADERS OR + (REFLECTCPP_JSON AND NOT REFLECTCPP_USE_BUNDLED_DEPENDENCIES) OR + REFLECTCPP_AVRO OR + REFLECTCPP_BSON OR + REFLECTCPP_CAPNPROTO OR + REFLECTCPP_CBOR OR + REFLECTCPP_CEREAL OR + REFLECTCPP_CSV OR + REFLECTCPP_FLEXBUFFERS OR + REFLECTCPP_MSGPACK OR + REFLECTCPP_PARQUET OR + REFLECTCPP_XML OR + REFLECTCPP_TOML OR + REFLECTCPP_UBJSON OR + REFLECTCPP_YAML +) # enable vcpkg per default if features other than JSON are required set(REFLECTCPP_USE_VCPKG_DEFAULT ON) endif() @@ -98,7 +114,11 @@ if (REFLECTCPP_USE_VCPKG) if (REFLECTCPP_CBOR OR REFLECTCPP_CHECK_HEADERS) list(APPEND VCPKG_MANIFEST_FEATURES "cbor") endif() - + + if (REFLECTCPP_CEREAL OR REFLECTCPP_CHECK_HEADERS) + list(APPEND VCPKG_MANIFEST_FEATURES "cereal") + endif() + if (REFLECTCPP_CSV OR REFLECTCPP_CHECK_HEADERS) list(APPEND VCPKG_MANIFEST_FEATURES "csv") endif() @@ -312,6 +332,16 @@ if (REFLECTCPP_CBOR OR REFLECTCPP_CHECK_HEADERS) include_directories(PUBLIC ${jsoncons_INCLUDE_DIRS}) endif () +if (REFLECTCPP_CEREAL OR REFLECTCPP_CHECK_HEADERS) + list(APPEND REFLECT_CPP_SOURCES + src/reflectcpp_cereal.cpp + ) + if (NOT TARGET cereal::cereal) + find_package(cereal CONFIG REQUIRED) + endif () + target_link_libraries(reflectcpp PUBLIC cereal::cereal) +endif() + if (REFLECTCPP_CSV OR REFLECTCPP_CHECK_HEADERS) if (NOT TARGET Arrow) find_package(Arrow CONFIG REQUIRED) diff --git a/README.md b/README.md index 9ac863e02..1232851a8 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ The following table lists the serialization formats currently supported by refle | BSON | [libbson](https://github.com/mongodb/mongo-c-driver) | >= 1.25.1 | Apache 2.0 | JSON-like binary format | | Cap'n Proto | [capnproto](https://capnproto.org) | >= 1.0.2 | MIT | Schemaful binary format | | CBOR | [jsoncons](https://github.com/danielaparker/jsoncons)| >= 0.176.0 | BSL 1.0 | JSON-like binary format | +| Cereal | [Cereal](https://uscilab.github.io/cereal/) | >= 1.3.2 | BSD | C++ serialization library with multiple formats | | CSV | [Apache Arrow](https://arrow.apache.org/) | >= 21.0.0 | Apache 2.0 | Tabular textual format | | flexbuffers | [flatbuffers](https://github.com/google/flatbuffers) | >= 23.5.26 | Apache 2.0 | Schema-less version of flatbuffers, binary format | | msgpack | [msgpack-c](https://github.com/msgpack/msgpack-c) | >= 6.0.0 | BSL 1.0 | JSON-like binary format | @@ -155,6 +156,7 @@ rfl::avro::write(homer); rfl::bson::write(homer); rfl::capnproto::write(homer); rfl::cbor::write(homer); +rfl::cereal::write(homer); rfl::flexbuf::write(homer); rfl::msgpack::write(homer); rfl::toml::write(homer); @@ -165,6 +167,7 @@ rfl::avro::read(avro_bytes); rfl::bson::read(bson_bytes); rfl::capnproto::read(capnproto_bytes); rfl::cbor::read(cbor_bytes); +rfl::cereal::read(cereal_bytes); rfl::flexbuf::read(flexbuf_bytes); rfl::msgpack::read(msgpack_bytes); rfl::toml::read(toml_string); diff --git a/conanfile.py b/conanfile.py index a085b7b64..ed8660d7f 100644 --- a/conanfile.py +++ b/conanfile.py @@ -40,6 +40,7 @@ class ReflectCppConan(ConanFile): "fPIC": [True, False], "with_capnproto": [True, False], "with_cbor": [True, False], + "with_cereal": [True, False], "with_csv": [True, False], "with_flatbuffers": [True, False], "with_msgpack": [True, False], @@ -54,6 +55,7 @@ class ReflectCppConan(ConanFile): "fPIC": True, "with_capnproto": False, "with_cbor": False, + "with_cereal": False, "with_csv": False, "with_flatbuffers": False, "with_msgpack": False, @@ -79,6 +81,8 @@ def requirements(self): self.requires("capnproto/1.1.0", transitive_headers=True) if self.options.with_cbor or self.options.with_ubjson: self.requires("jsoncons/0.176.0", transitive_headers=True) + if self.options.with_cereal: + self.requires("cereal/1.3.2", transitive_headers=True) if self.options.with_csv or self.options.with_parquet: self.requires("arrow/21.0.0", transitive_headers=True) if self.options.with_flatbuffers: @@ -116,6 +120,7 @@ def generate(self): tc.cache_variables["REFLECTCPP_USE_VCPKG"] = False tc.cache_variables["REFLECTCPP_CAPNPROTO"] = self.options.with_capnproto tc.cache_variables["REFLECTCPP_CBOR"] = self.options.with_cbor + tc.cache_variables["REFLECTCPP_CEREAL"] = self.options.with_cereal tc.cache_variables["REFLECTCPP_CSV"] = self.options.with_csv tc.cache_variables["REFLECTCPP_FLEXBUFFERS"] = self.options.with_flatbuffers tc.cache_variables["REFLECTCPP_MSGPACK"] = self.options.with_msgpack diff --git a/docs/supported_formats/cereal.md b/docs/supported_formats/cereal.md new file mode 100644 index 000000000..eff62bb01 --- /dev/null +++ b/docs/supported_formats/cereal.md @@ -0,0 +1,135 @@ +# Cereal + +For Cereal support, you must also include the header `` and link to the [Cereal](https://uscilab.github.io/cereal/) library. +Furthermore, when compiling reflect-cpp, you need to pass `-DREFLECTCPP_CEREAL=ON` to cmake. + +Cereal is a C++ serialization library that provides multiple archive formats including binary, portable binary, JSON, and XML formats. + +## Reading and writing + +Suppose you have a struct like this: + +```cpp +struct Person { + std::string first_name; + std::string last_name; + rfl::Timestamp<"%Y-%m-%d"> birthday; + std::vector children; +}; +``` + +A `Person` struct can be serialized to a bytes vector like this: + +```cpp +const auto person = Person{...}; +const std::vector bytes = rfl::cereal::write(person); +``` + +You can parse bytes like this: + +```cpp +const rfl::Result result = rfl::cereal::read(bytes); +``` + +## Loading and saving + +You can also load and save to disc using a very similar syntax: + +```cpp +const rfl::Result result = rfl::cereal::load("/path/to/file.cereal"); + +const auto person = Person{...}; +rfl::cereal::save("/path/to/file.cereal", person); +``` + +## Reading from and writing into streams + +You can also read from and write into any `std::istream` and `std::ostream` respectively. + +```cpp +const rfl::Result result = rfl::cereal::read(my_istream); + +const auto person = Person{...}; +rfl::cereal::write(person, my_ostream); +``` + +Note that `std::cout` is also an ostream, so this works as well: + +```cpp +rfl::cereal::write(person, std::cout) << std::endl; +``` + +(Since Cereal binary format is a binary format, the readability of this will be limited, but it might be useful for debugging). + +## Using different Cereal archive formats + +By default, reflect-cpp uses Cereal's `BinaryOutputArchive` and `BinaryInputArchive`. However, you can use other archive formats by using the template functions directly: + +```cpp +#include +#include +#include +#include + +const auto person = Person{...}; + +// Using JSON archive +std::stringstream json_stream; +{ + cereal::JSONOutputArchive archive(json_stream); + rfl::cereal::write(person, archive); +} + +// Using XML archive +std::stringstream xml_stream; +{ + cereal::XMLOutputArchive archive(xml_stream); + rfl::cereal::write(person, archive); +} + +// Using Portable Binary archive (endian-safe) +std::stringstream pb_stream; +{ + cereal::PortableBinaryOutputArchive archive(pb_stream); + rfl::cereal::write(person, archive); +} +``` + +## Custom constructors + +One of the great things about C++ is that it gives you control over +when and how you code is compiled. + +For large and complex systems of structs, it is often a good idea to split up +your code into smaller compilation units. You can do so using custom constructors. + +For the Cereal format, these must be a static function on your struct or class called +`from_cereal_obj` that take a `rfl::cereal::Reader::InputVarType` as input and return +the class or the class wrapped in `rfl::Result`. + +In your header file you can write something like this: + +```cpp +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name; + rfl::Timestamp<"%Y-%m-%d"> birthday; + + using InputVarType = typename rfl::cereal::Reader::InputVarType; + static rfl::Result from_cereal_obj(const InputVarType& _obj); +}; +``` + +And in your source file, you implement `from_cereal_obj` as follows: + +```cpp +rfl::Result Person::from_cereal_obj(const InputVarType& _obj) { + const auto from_nt = [](auto&& _nt) { + return rfl::from_named_tuple(std::move(_nt)); + }; + return rfl::cereal::read>(*_obj.archive_) + .transform(from_nt); +} +``` + +This will force the compiler to only compile the Cereal parsing when the source file is compiled. diff --git a/include/rfl/cereal.hpp b/include/rfl/cereal.hpp new file mode 100644 index 000000000..f4c3bc62e --- /dev/null +++ b/include/rfl/cereal.hpp @@ -0,0 +1,13 @@ +#ifndef RFL_CEREAL_HPP_ +#define RFL_CEREAL_HPP_ + +#include "../rfl.hpp" +#include "cereal/Parser.hpp" +#include "cereal/Reader.hpp" +#include "cereal/Writer.hpp" +#include "cereal/load.hpp" +#include "cereal/read.hpp" +#include "cereal/save.hpp" +#include "cereal/write.hpp" + +#endif diff --git a/include/rfl/cereal/Parser.hpp b/include/rfl/cereal/Parser.hpp new file mode 100644 index 000000000..9b7cd0a24 --- /dev/null +++ b/include/rfl/cereal/Parser.hpp @@ -0,0 +1,54 @@ +#ifndef RFL_CEREAL_PARSER_HPP_ +#define RFL_CEREAL_PARSER_HPP_ + +#include "../parsing/Parser.hpp" +#include "Reader.hpp" +#include "Writer.hpp" + +namespace rfl { +namespace parsing { + +/// Cereal requires us to explicitly set the number of fields in advance. +/// Because of that, we require all of the fields and then set them to nullptr, +/// if necessary. +template + requires AreReaderAndWriter> +struct Parser, + ProcessorsType> + : public NamedTupleParser< + cereal::Reader, cereal::Writer, + /*_ignore_empty_containers=*/false, + /*_all_required=*/true, + /*_no_field_names=*/ProcessorsType::no_field_names_, ProcessorsType, + FieldTypes...> {}; + +template + requires AreReaderAndWriter> +struct Parser, ProcessorsType> + : public TupleParser> {}; + +template + requires AreReaderAndWriter> +struct Parser, ProcessorsType> + : public TupleParser> {}; + +} // namespace parsing +} // namespace rfl + +namespace rfl { +namespace cereal { + +template +using Parser = parsing::Parser; + +} +} // namespace rfl + +#endif diff --git a/include/rfl/cereal/Reader.hpp b/include/rfl/cereal/Reader.hpp new file mode 100644 index 000000000..3905baa9c --- /dev/null +++ b/include/rfl/cereal/Reader.hpp @@ -0,0 +1,108 @@ +#ifndef RFL_CEREAL_READER_HPP_ +#define RFL_CEREAL_READER_HPP_ + +#include +#include +#include +#include +#include +#include + +#include "../Result.hpp" +#include "../always_false.hpp" + +namespace rfl::cereal { + +struct Reader { + using CerealArchive = ::cereal::PortableBinaryInputArchive; + + struct CerealInputVar { + CerealArchive* archive_; + }; + + struct InputVarType { + CerealArchive* archive_; + }; + + struct InputArrayType { + CerealArchive* archive_; + }; + + struct InputObjectType { + CerealArchive* archive_; + }; + + template + static constexpr bool has_custom_constructor = + (requires(InputVarType var) { T::from_cereal_obj(var); }); + + bool is_empty(const InputVarType& _var) const noexcept { + return _var.archive_ == nullptr; + } + + template + rfl::Result to_basic_type(const InputVarType& _var) const noexcept { + try { + T value; + (*_var.archive_)(value); + return value; + } catch (std::exception& e) { + return error(std::string("Cereal read error: ") + e.what()); + } + } + + rfl::Result to_array( + const InputVarType& _var) const noexcept { + return InputArrayType{_var.archive_}; + } + + rfl::Result to_object( + const InputVarType& _var) const noexcept { + return InputObjectType{_var.archive_}; + } + + template + std::optional read_array(const ArrayReader& _array_reader, + const InputArrayType& _arr) const noexcept { + try { + ::cereal::size_type size; + (*_arr.archive_)(::cereal::make_size_tag(size)); + for (::cereal::size_type i = 0; i < size; ++i) { + const auto err = _array_reader.read(InputVarType{_arr.archive_}); + if (err) { + return err; + } + } + return std::nullopt; + } catch (std::exception& e) { + return Error(std::string("Cereal array read error: ") + e.what()); + } + } + + template + std::optional read_object(const ObjectReader& _object_reader, + const InputObjectType& _obj) const noexcept { + try { + _object_reader.read([&](const auto& _field) { + (*_obj.archive_)(::cereal::make_nvp(_field.name(), _field.get())); + }); + return std::nullopt; + } catch (std::exception& e) { + return Error(std::string("Cereal object read error: ") + e.what()); + } + } + + template + rfl::Result use_custom_constructor( + const InputVarType& _var) const noexcept { + try { + return T::from_cereal_obj(_var); + } catch (std::exception& e) { + return error(e.what()); + } + } +}; + +} // namespace rfl::cereal + +#endif diff --git a/include/rfl/cereal/Writer.hpp b/include/rfl/cereal/Writer.hpp new file mode 100644 index 000000000..f1f8a01fe --- /dev/null +++ b/include/rfl/cereal/Writer.hpp @@ -0,0 +1,107 @@ +#ifndef RFL_CEREAL_WRITER_HPP_ +#define RFL_CEREAL_WRITER_HPP_ + +#include +#include +#include +#include +#include +#include + +#include "../always_false.hpp" +#include "../common.hpp" + +namespace rfl::cereal { + +class Writer { + public: + using CerealArchive = ::cereal::PortableBinaryOutputArchive; + + struct CerealOutputArray {}; + + struct CerealOutputObject {}; + + struct CerealOutputVar {}; + + using OutputArrayType = CerealOutputArray; + using OutputObjectType = CerealOutputObject; + using OutputVarType = CerealOutputVar; + + Writer(CerealArchive* _archive) : archive_(_archive) {} + + ~Writer() = default; + + OutputArrayType array_as_root(const size_t _size) const { + (*archive_)(::cereal::make_size_tag(_size)); + return OutputArrayType{}; + } + + OutputObjectType object_as_root(const size_t _size) const { + return OutputObjectType{}; + } + + OutputVarType null_as_root() const { return OutputVarType{}; } + + template + OutputVarType value_as_root(const T& _var) const { + (*archive_)(_var); + return OutputVarType{}; + } + + OutputArrayType add_array_to_array(const size_t _size, + OutputArrayType* _parent) const { + (*archive_)(::cereal::make_size_tag(_size)); + return OutputArrayType{}; + } + + OutputArrayType add_array_to_object(const std::string_view& _name, + const size_t _size, + OutputObjectType* _parent) const { + (*archive_)(::cereal::make_size_tag(_size)); + return OutputArrayType{}; + } + + OutputObjectType add_object_to_array(const size_t _size, + OutputArrayType* _parent) const { + return OutputObjectType{}; + } + + OutputObjectType add_object_to_object(const std::string_view& _name, + const size_t _size, + OutputObjectType* _parent) const { + return OutputObjectType{}; + } + + template + OutputVarType add_value_to_array(const T& _var, OutputArrayType*) const { + (*archive_)(_var); + return OutputVarType{}; + } + + template + OutputVarType add_value_to_object(const std::string_view& _name, + const T& _var, OutputObjectType*) const { + (*archive_)(::cereal::make_nvp(_name.data(), _var)); + return OutputVarType{}; + } + + OutputVarType add_null_to_array(OutputArrayType* _parent) const { + return OutputVarType{}; + } + + OutputVarType add_null_to_object(const std::string_view& _name, + OutputObjectType* _parent) const { + return OutputVarType{}; + } + + void end_array(OutputArrayType* _arr) const noexcept {} + + void end_object(OutputObjectType* _obj) const noexcept {} + + private: + CerealArchive* archive_; +}; + +} // namespace rfl::cereal + +#endif diff --git a/include/rfl/cereal/load.hpp b/include/rfl/cereal/load.hpp new file mode 100644 index 000000000..c026375ec --- /dev/null +++ b/include/rfl/cereal/load.hpp @@ -0,0 +1,22 @@ +#ifndef RFL_CEREAL_LOAD_HPP_ +#define RFL_CEREAL_LOAD_HPP_ + +#include "../Result.hpp" +#include "../io/load_bytes.hpp" +#include "read.hpp" + +namespace rfl { +namespace cereal { + +template +Result load(const std::string& _fname) { + const auto read_bytes = [](const auto& _bytes) { + return read(_bytes); + }; + return rfl::io::load_bytes(_fname).and_then(read_bytes); +} + +} // namespace cereal +} // namespace rfl + +#endif diff --git a/include/rfl/cereal/read.hpp b/include/rfl/cereal/read.hpp new file mode 100644 index 000000000..9d2e1d3e9 --- /dev/null +++ b/include/rfl/cereal/read.hpp @@ -0,0 +1,61 @@ +#ifndef RFL_CEREAL_READ_HPP_ +#define RFL_CEREAL_READ_HPP_ + +#include +#include +#include +#include + +#include "../Processors.hpp" +#include "../concepts.hpp" +#include "../internal/wrap_in_rfl_array_t.hpp" +#include "Parser.hpp" +#include "Reader.hpp" + +namespace rfl::cereal { + +using InputVarType = Reader::InputVarType; + +/// Parses an object from a Cereal InputArchive. +template +auto read(Reader::CerealArchive& _archive) { + const auto r = Reader(); + auto var = InputVarType{&_archive}; + return Parser>::read(r, var); +} + +/// Parses an object from Cereal binary format using reflection. +template +Result> read( + const concepts::ByteLike auto* _bytes, const size_t _size) { + try { + std::stringstream ss( + std::string(reinterpret_cast(_bytes), _size)); + ::cereal::PortableBinaryInputArchive archive(ss); + return read(archive); + } catch (std::exception& e) { + return error(std::string("Cereal read error: ") + e.what()); + } +} + +/// Parses an object from Cereal binary format using reflection. +template +auto read(const concepts::ContiguousByteContainer auto& _bytes) { + return read(_bytes.data(), _bytes.size()); +} + +/// Parses an object from a stream using Cereal binary format. +template +auto read(std::istream& _stream) { + try { + ::cereal::BinaryInputArchive archive(_stream); + return read(archive); + } catch (std::exception& e) { + return Result>( + error(std::string("Cereal read error: ") + e.what())); + } +} + +} // namespace rfl::cereal + +#endif diff --git a/include/rfl/cereal/save.hpp b/include/rfl/cereal/save.hpp new file mode 100644 index 000000000..09a9f6628 --- /dev/null +++ b/include/rfl/cereal/save.hpp @@ -0,0 +1,24 @@ +#ifndef RFL_CEREAL_SAVE_HPP_ +#define RFL_CEREAL_SAVE_HPP_ + +#include + +#include "../Result.hpp" +#include "../io/save_bytes.hpp" +#include "write.hpp" + +namespace rfl { +namespace cereal { + +template +Result save(const std::string& _fname, const auto& _obj) { + const auto write_func = [](const auto& _obj, auto& _stream) -> auto& { + return write(_obj, _stream); + }; + return rfl::io::save_bytes(_fname, _obj, write_func); +} + +} // namespace cereal +} // namespace rfl + +#endif diff --git a/include/rfl/cereal/write.hpp b/include/rfl/cereal/write.hpp new file mode 100644 index 000000000..ca9d8aec0 --- /dev/null +++ b/include/rfl/cereal/write.hpp @@ -0,0 +1,47 @@ +#ifndef RFL_CEREAL_WRITE_HPP_ +#define RFL_CEREAL_WRITE_HPP_ + +#include +#include + +#include "../Processors.hpp" +#include "../Result.hpp" +#include "../parsing/Parent.hpp" +#include "Parser.hpp" +#include "Writer.hpp" + +namespace rfl::cereal { + +/// Writes an object to a Cereal OutputArchive. +template +void write(const T& _obj, Writer::CerealArchive& _archive) { + using ParentType = parsing::Parent; + auto w = Writer(&_archive); + Parser>::write(w, _obj, typename ParentType::Root{}); +} + +/// Returns Cereal binary bytes. +template +std::vector write(const auto& _obj) { + std::stringstream ss; + { + ::cereal::PortableBinaryOutputArchive archive(ss); + write, decltype(archive), Ps...>( + _obj, archive); + } + auto str = ss.str(); + return std::vector(str.begin(), str.end()); +} + +/// Writes Cereal binary format into an ostream. +template +std::ostream& write(const auto& _obj, std::ostream& _stream) { + ::cereal::BinaryOutputArchive archive(_stream); + write, decltype(archive), Ps...>(_obj, + archive); + return _stream; +} + +} // namespace rfl::cereal + +#endif diff --git a/mkdocs.yaml b/mkdocs.yaml index 24e7e1fd8..cc55300fa 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -6,7 +6,7 @@ site_url: https://rfl.getml.com/ site_author: Code17 GmbH site_description: >- A C++20 library for fast serialization, deserialization and validation using reflection. - Supports JSON, AVRO, BSON, Cap'n Proto, CBOR, CSV, flexbuffers, msgpack, parquet, TOML, UBJSON, XML, YAML + Supports JSON, AVRO, BSON, Cap'n Proto, CBOR, Cereal, CSV, flexbuffers, msgpack, parquet, TOML, UBJSON, XML, YAML theme: name: "material" @@ -96,6 +96,7 @@ nav: - BSON: supported_formats/bson.md - Cap'n Proto: supported_formats/capnproto.md - CBOR: supported_formats/cbor.md + - Cereal: supported_formats/cereal.md - CSV: supported_formats/csv.md - FlexBuffers: supported_formats/flexbuffers.md - JSON: supported_formats/json.md diff --git a/reflectcpp-config.cmake.in b/reflectcpp-config.cmake.in index 128a89c70..d401db03c 100644 --- a/reflectcpp-config.cmake.in +++ b/reflectcpp-config.cmake.in @@ -4,6 +4,7 @@ set(REFLECTCPP_JSON @REFLECTCPP_JSON@) set(REFLECTCPP_BSON @REFLECTCPP_BSON@) set(REFLECTCPP_CAPNPROTO @REFLECTCPP_CAPNPROTO@) set(REFLECTCPP_CBOR @REFLECTCPP_CBOR@) +set(REFLECTCPP_CEREAL @REFLECTCPP_CEREAL@) set(REFLECTCPP_CSV @REFLECTCPP_CSV@) set(REFLECTCPP_FLEXBUFFERS @REFLECTCPP_FLEXBUFFERS@) set(REFLECTCPP_MSGPACK @REFLECTCPP_MSGPACK@) @@ -14,7 +15,7 @@ set(REFLECTCPP_XML @REFLECTCPP_XML@) set(REFLECTCPP_YAML @REFLECTCPP_YAML@) set(REFLECTCPP_USE_BUNDLED_DEPENDENCIES @REFLECTCPP_USE_BUNDLED_DEPENDENCIES@) -if(REFLECTCPP_BSON OR REFLECTCPP_CAPNPROTO OR REFLECTCPP_CBOR OR REFLECTCPP_FLEXBUFFERS OR REFLECTCPP_MSGPACK OR REFLECTCPP_TOML OR REFLECTCPP_UBJSON OR REFLECTCPP_XML OR REFLECTCPP_YAML OR (REFLECTCPP_JSON AND NOT REFLECTCPP_USE_BUNDLED_DEPENDENCIES)) +if(REFLECTCPP_BSON OR REFLECTCPP_CAPNPROTO OR REFLECTCPP_CBOR OR REFLECTCPP_CEREAL OR REFLECTCPP_FLEXBUFFERS OR REFLECTCPP_MSGPACK OR REFLECTCPP_TOML OR REFLECTCPP_UBJSON OR REFLECTCPP_XML OR REFLECTCPP_YAML OR (REFLECTCPP_JSON AND NOT REFLECTCPP_USE_BUNDLED_DEPENDENCIES)) include(CMakeFindDependencyMacro) endif() @@ -40,6 +41,10 @@ if (REFLECTCPP_CBOR OR REFLECTCPP_UBJSON) find_dependency(jsoncons) endif () +if (REFLECTCPP_CEREAL) + find_dependency(cereal) +endif () + if (REFLECTCPP_CSV) find_dependency(Arrow) endif() diff --git a/src/reflectcpp_cereal.cpp b/src/reflectcpp_cereal.cpp new file mode 100644 index 000000000..ec1ff8498 --- /dev/null +++ b/src/reflectcpp_cereal.cpp @@ -0,0 +1,32 @@ +/* + +MIT License + +Copyright (c) 2023-2024 Code17 GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +// This file include all other source files, so that the user of the library +// don't need to add multiple source files into their build. +// Also, this speeds up compile time, compared to multiple separate .cpp files +// compilation. + +// Cereal is header-only, so this file is minimal diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9f3c2a908..bc5aaee1d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -30,6 +30,10 @@ if (REFLECTCPP_CBOR) add_subdirectory(cbor) endif() +if (REFLECTCPP_CEREAL) + add_subdirectory(cereal) +endif() + if (REFLECTCPP_CSV) add_subdirectory(csv) endif() diff --git a/tests/cereal/CMakeLists.txt b/tests/cereal/CMakeLists.txt new file mode 100644 index 000000000..f6a8a2928 --- /dev/null +++ b/tests/cereal/CMakeLists.txt @@ -0,0 +1,20 @@ +project(reflect-cpp-cereal-tests) + +file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "*.cpp") + +add_executable( + reflect-cpp-cereal-tests + ${SOURCES} +) +target_precompile_headers(reflect-cpp-cereal-tests PRIVATE [["rfl.hpp"]] ) + + +target_link_libraries(reflect-cpp-cereal-tests PRIVATE reflectcpp_tests_crt) + +add_custom_command(TARGET reflect-cpp-cereal-tests POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy -t $ $ + COMMAND_EXPAND_LISTS +) + +find_package(GTest) +gtest_discover_tests(reflect-cpp-cereal-tests) diff --git a/tests/cereal/test_array.cpp b/tests/cereal/test_array.cpp new file mode 100644 index 000000000..81412d435 --- /dev/null +++ b/tests/cereal/test_array.cpp @@ -0,0 +1,32 @@ +#include +#include +#include +#include + +#include + +#include "write_and_read.hpp" + +namespace test_array { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::unique_ptr> children = nullptr; +}; + +TEST(cereal, test_array) { + auto bart = Person{.first_name = "Bart"}; + + auto lisa = Person{.first_name = "Lisa"}; + + auto maggie = Person{.first_name = "Maggie"}; + + const auto homer = Person{ + .first_name = "Homer", + .children = std::make_unique>(std::array{ + std::move(bart), std::move(lisa), std::move(maggie)})}; + + write_and_read(homer); +} +} // namespace test_array diff --git a/tests/cereal/test_box.cpp b/tests/cereal/test_box.cpp new file mode 100644 index 000000000..06c44cb22 --- /dev/null +++ b/tests/cereal/test_box.cpp @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_box { + +struct DecisionTree { + struct Leaf { + using Tag = rfl::Literal<"Leaf">; + double value; + }; + + struct Node { + using Tag = rfl::Literal<"Node">; + rfl::Rename<"criticalValue", double> critical_value; + rfl::Box lesser; + rfl::Box greater; + }; + + using LeafOrNode = rfl::TaggedUnion<"type", Leaf, Node>; + + rfl::Field<"leafOrNode", LeafOrNode> leaf_or_node; +}; + +TEST(cereal, test_box) { + auto leaf1 = DecisionTree::Leaf{.value = 3.0}; + + auto leaf2 = DecisionTree::Leaf{.value = 5.0}; + + auto node = DecisionTree::Node{ + .critical_value = 10.0, + .lesser = rfl::make_box(DecisionTree::LeafOrNode(leaf1)), + .greater = rfl::make_box(DecisionTree::LeafOrNode(leaf2))}; + + const DecisionTree tree = DecisionTree{.leaf_or_node = std::move(node)}; + + write_and_read(tree); +} +} // namespace test_box diff --git a/tests/cereal/test_enum.cpp b/tests/cereal/test_enum.cpp new file mode 100644 index 000000000..925200726 --- /dev/null +++ b/tests/cereal/test_enum.cpp @@ -0,0 +1,22 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_enum { + +enum class Color { red, green, blue, yellow }; + +struct Circle { + double radius; + Color color; +}; + +TEST(cereal, test_enum) { + const auto circle = Circle{.radius = 5.0, .color = Color::green}; + write_and_read(circle); +} +} // namespace test_enum diff --git a/tests/cereal/test_map.cpp b/tests/cereal/test_map.cpp new file mode 100644 index 000000000..b9a1dfee0 --- /dev/null +++ b/tests/cereal/test_map.cpp @@ -0,0 +1,34 @@ +#include +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_map { +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::unique_ptr> children; +}; + +TEST(cereal, test_map) { + auto bart = Person{.first_name = "Bart"}; + + auto lisa = Person{.first_name = "Lisa"}; + + auto maggie = Person{.first_name = "Maggie"}; + + auto children = std::make_unique>( + std::map({{"Bart", std::move(bart)}, + {"Lisa", std::move(lisa)}, + {"Maggie", std::move(maggie)}})); + + const auto homer = + Person{.first_name = "Homer", .children = std::move(children)}; + + write_and_read(homer); +} +} // namespace test_map diff --git a/tests/cereal/test_optional_fields.cpp b/tests/cereal/test_optional_fields.cpp new file mode 100644 index 000000000..c1ce79a23 --- /dev/null +++ b/tests/cereal/test_optional_fields.cpp @@ -0,0 +1,24 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_optional_fields { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name; + std::optional town = std::nullopt; +}; + +TEST(cereal, test_optional_fields) { + const auto person1 = Person{.first_name = "Homer", .last_name = "Simpson"}; + write_and_read(person1); + + const auto person2 = Person{ + .first_name = "Homer", .last_name = "Simpson", .town = "Springfield"}; + write_and_read(person2); +} +} // namespace test_optional_fields diff --git a/tests/cereal/test_person.cpp b/tests/cereal/test_person.cpp new file mode 100644 index 000000000..aa33cd539 --- /dev/null +++ b/tests/cereal/test_person.cpp @@ -0,0 +1,31 @@ +#include + +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_person { + +struct Person { + std::string first_name; + std::string last_name = "Simpson"; + std::vector children; +}; + +TEST(cereal, test_person) { + const auto bart = Person{.first_name = "Bart"}; + + const auto lisa = Person{.first_name = "Lisa"}; + + const auto maggie = Person{.first_name = "Maggie"}; + + const auto homer = + Person{.first_name = "Homer", + .children = std::vector({bart, lisa, maggie})}; + + write_and_read(homer); +} +} // namespace test_person diff --git a/tests/cereal/test_readme_example.cpp b/tests/cereal/test_readme_example.cpp new file mode 100644 index 000000000..9375dced9 --- /dev/null +++ b/tests/cereal/test_readme_example.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_readme_example { + +using Age = rfl::Validator, rfl::Maximum<130>>; + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::string town = "Springfield"; + rfl::Timestamp<"%Y-%m-%d"> birthday; + Age age; + rfl::Email email; + std::vector child; +}; + +TEST(cereal, test_readme_example) { + const auto bart = Person{.first_name = "Bart", + .birthday = "1987-04-19", + .age = 10, + .email = "bart@simpson.com"}; + + const auto lisa = Person{.first_name = "Lisa", + .birthday = "1987-04-19", + .age = 8, + .email = "lisa@simpson.com"}; + + const auto maggie = Person{.first_name = "Maggie", + .birthday = "1987-04-19", + .age = 0, + .email = "maggie@simpson.com"}; + + const auto homer = Person{.first_name = "Homer", + .birthday = "1987-04-19", + .age = 45, + .email = "homer@simpson.com", + .child = std::vector({bart, lisa, maggie})}; + + write_and_read(homer); +} +} // namespace test_readme_example diff --git a/tests/cereal/test_save_load.cpp b/tests/cereal/test_save_load.cpp new file mode 100644 index 000000000..16c878cd6 --- /dev/null +++ b/tests/cereal/test_save_load.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_save_load { +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name; + rfl::Email email; + int age; +}; + +TEST(cereal, test_save_load) { + const auto homer = + Person{.first_name = "Homer", + .last_name = "Simpson", + .email = "homer@simpson.com", + .age = 45}; + + rfl::cereal::save("/tmp/homer.cereal", homer); + + const auto homer2 = rfl::cereal::load("/tmp/homer.cereal").value(); + + write_and_read(homer2); +} +} // namespace test_save_load diff --git a/tests/cereal/test_tagged_union.cpp b/tests/cereal/test_tagged_union.cpp new file mode 100644 index 000000000..00eb5baf2 --- /dev/null +++ b/tests/cereal/test_tagged_union.cpp @@ -0,0 +1,34 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_tagged_union { + +struct Circle { + using Tag = rfl::Literal<"circle">; + double radius; +}; + +struct Rectangle { + using Tag = rfl::Literal<"rectangle">; + double height; + double width; +}; + +struct Square { + using Tag = rfl::Literal<"square">; + double width; +}; + +using Shapes = rfl::TaggedUnion<"shape", Circle, Rectangle, Square>; + +TEST(cereal, test_tagged_union) { + const Shapes r = Rectangle{.height = 10, .width = 5}; + write_and_read(r); +} + +} // namespace test_tagged_union diff --git a/tests/cereal/test_timestamp.cpp b/tests/cereal/test_timestamp.cpp new file mode 100644 index 000000000..42cbe3ade --- /dev/null +++ b/tests/cereal/test_timestamp.cpp @@ -0,0 +1,24 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_timestamp { + +struct Person { + std::string first_name; + std::string last_name; + rfl::Timestamp<"%Y-%m-%d"> birthday; +}; + +TEST(cereal, test_timestamp) { + const auto person = Person{.first_name = "Homer", + .last_name = "Simpson", + .birthday = "1987-04-19"}; + + write_and_read(person); +} +} // namespace test_timestamp diff --git a/tests/cereal/test_tuple.cpp b/tests/cereal/test_tuple.cpp new file mode 100644 index 000000000..55459a2dc --- /dev/null +++ b/tests/cereal/test_tuple.cpp @@ -0,0 +1,18 @@ +#include +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_tuple { + +TEST(cereal, test_tuple) { + using TupleType = std::tuple; + const auto my_tuple = std::make_tuple(42, std::string("Hello"), true, 3.14); + write_and_read(my_tuple); +} + +} // namespace test_tuple diff --git a/tests/cereal/test_unique_ptr.cpp b/tests/cereal/test_unique_ptr.cpp new file mode 100644 index 000000000..588643ccc --- /dev/null +++ b/tests/cereal/test_unique_ptr.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_unique_ptr { + +struct DecisionTree { + struct Leaf { + using Tag = rfl::Literal<"Leaf">; + double value; + }; + + struct Node { + using Tag = rfl::Literal<"Node">; + rfl::Rename<"criticalValue", double> critical_value; + std::unique_ptr lesser; + std::unique_ptr greater; + }; + + using LeafOrNode = rfl::TaggedUnion<"type", Leaf, Node>; + + rfl::Field<"leafOrNode", LeafOrNode> leaf_or_node; +}; + +TEST(cereal, test_unique_ptr) { + auto leaf1 = DecisionTree::Leaf{.value = 3.0}; + + auto leaf2 = DecisionTree::Leaf{.value = 5.0}; + + auto node = DecisionTree::Node{ + .critical_value = 10.0, + .lesser = std::make_unique(DecisionTree::LeafOrNode(leaf1)), + .greater = std::make_unique(DecisionTree::LeafOrNode(leaf2))}; + + const DecisionTree tree = DecisionTree{.leaf_or_node = std::move(node)}; + + write_and_read(tree); +} +} // namespace test_unique_ptr diff --git a/tests/cereal/test_variant.cpp b/tests/cereal/test_variant.cpp new file mode 100644 index 000000000..b2c7e3d13 --- /dev/null +++ b/tests/cereal/test_variant.cpp @@ -0,0 +1,32 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_variant { + +struct Circle { + double radius; +}; + +struct Rectangle { + double height; + double width; +}; + +struct Square { + double width; +}; + +using Shapes = std::variant; + +TEST(cereal, test_variant) { + const Shapes r = Rectangle{.height = 10, .width = 5}; + + write_and_read(r); +} + +} // namespace test_variant diff --git a/tests/cereal/write_and_read.hpp b/tests/cereal/write_and_read.hpp new file mode 100644 index 000000000..fc84a002a --- /dev/null +++ b/tests/cereal/write_and_read.hpp @@ -0,0 +1,18 @@ +#ifndef WRITE_AND_READ_ +#define WRITE_AND_READ_ + +#include + +#include + +template +void write_and_read(const auto& _struct) { + using T = std::remove_cvref_t; + const auto serialized1 = rfl::cereal::write(_struct); + const auto res = rfl::cereal::read(serialized1); + EXPECT_TRUE(res && true) << "Test failed on read. Error: " + << res.error().what(); + const auto serialized2 = rfl::cereal::write(res.value()); + EXPECT_EQ(serialized1, serialized2); +} +#endif diff --git a/vcpkg.json b/vcpkg.json index 577903832..644044443 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -70,6 +70,15 @@ } ] }, + "cereal": { + "description": "Enable Cereal support", + "dependencies": [ + { + "name": "cereal", + "version>=": "1.3.2#1" + } + ] + }, "csv": { "description": "Enable CSV support", "dependencies": [ From 6bba87f7e13f2615f4d5039fe79c1ac6387584b4 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 29 Mar 2026 14:48:16 +0200 Subject: [PATCH 02/42] More fixes --- include/rfl/cereal/Reader.hpp | 60 +++++++++++++++++- include/rfl/cereal/Writer.hpp | 111 ++++++++++++++++++++++++++++++++++ include/rfl/cereal/read.hpp | 4 +- include/rfl/cereal/write.hpp | 6 +- tests/cereal/test_box.cpp | 3 +- tests/cereal/test_enum.cpp | 5 +- 6 files changed, 177 insertions(+), 12 deletions(-) diff --git a/include/rfl/cereal/Reader.hpp b/include/rfl/cereal/Reader.hpp index 3905baa9c..7f01cb9b3 100644 --- a/include/rfl/cereal/Reader.hpp +++ b/include/rfl/cereal/Reader.hpp @@ -10,6 +10,7 @@ #include "../Result.hpp" #include "../always_false.hpp" +#include "../parsing/schemaful/IsSchemafulReader.hpp" namespace rfl::cereal { @@ -32,6 +33,14 @@ struct Reader { CerealArchive* archive_; }; + struct InputMapType { + CerealArchive* archive_; + }; + + struct InputUnionType { + CerealArchive* archive_; + }; + template static constexpr bool has_custom_constructor = (requires(InputVarType var) { T::from_cereal_obj(var); }); @@ -61,6 +70,15 @@ struct Reader { return InputObjectType{_var.archive_}; } + rfl::Result to_map(const InputVarType& _var) const noexcept { + return InputMapType{_var.archive_}; + } + + rfl::Result to_union( + const InputVarType& _var) const noexcept { + return InputUnionType{_var.archive_}; + } + template std::optional read_array(const ArrayReader& _array_reader, const InputArrayType& _arr) const noexcept { @@ -79,19 +97,53 @@ struct Reader { } } + template + std::optional read_map(const MapReader& _map_reader, + const InputMapType& _map) const noexcept { + try { + ::cereal::size_type size; + (*_map.archive_)(::cereal::make_size_tag(size)); + for (::cereal::size_type i = 0; i < size; ++i) { + std::string key; + (*_map.archive_)(key); + const auto err = _map_reader.read(std::string_view(key), + InputVarType{_map.archive_}); + if (err) { + return err; + } + } + return std::nullopt; + } catch (std::exception& e) { + return Error(std::string("Cereal map read error: ") + e.what()); + } + } + template std::optional read_object(const ObjectReader& _object_reader, const InputObjectType& _obj) const noexcept { try { - _object_reader.read([&](const auto& _field) { - (*_obj.archive_)(::cereal::make_nvp(_field.name(), _field.get())); - }); + ::cereal::size_type size; + (*_obj.archive_)(::cereal::make_size_tag(size)); + for (::cereal::size_type i = 0; i < size; ++i) { + _object_reader.read(i, InputVarType{_obj.archive_}); + } return std::nullopt; } catch (std::exception& e) { return Error(std::string("Cereal object read error: ") + e.what()); } } + template + rfl::Result read_union(const InputUnionType& _union) const noexcept { + try { + std::int32_t index; + (*_union.archive_)(index); + return UnionReader::read(*this, index, InputVarType{_union.archive_}); + } catch (std::exception& e) { + return error(std::string("Cereal union read error: ") + e.what()); + } + } + template rfl::Result use_custom_constructor( const InputVarType& _var) const noexcept { @@ -103,6 +155,8 @@ struct Reader { } }; +static_assert(parsing::schemaful::IsSchemafulReader); + } // namespace rfl::cereal #endif diff --git a/include/rfl/cereal/Writer.hpp b/include/rfl/cereal/Writer.hpp index f1f8a01fe..4db05da5d 100644 --- a/include/rfl/cereal/Writer.hpp +++ b/include/rfl/cereal/Writer.hpp @@ -10,6 +10,7 @@ #include "../always_false.hpp" #include "../common.hpp" +#include "../parsing/schemaful/IsSchemafulWriter.hpp" namespace rfl::cereal { @@ -19,12 +20,18 @@ class Writer { struct CerealOutputArray {}; + struct CerealOutputMap {}; + struct CerealOutputObject {}; + struct CerealOutputUnion {}; + struct CerealOutputVar {}; using OutputArrayType = CerealOutputArray; using OutputObjectType = CerealOutputObject; + using OutputMapType = CerealOutputMap; + using OutputUnionType = CerealOutputUnion; using OutputVarType = CerealOutputVar; Writer(CerealArchive* _archive) : archive_(_archive) {} @@ -36,10 +43,17 @@ class Writer { return OutputArrayType{}; } + OutputMapType map_as_root(const size_t _size) const { + (*archive_)(::cereal::make_size_tag(_size)); + return OutputMapType{}; + } + OutputObjectType object_as_root(const size_t _size) const { return OutputObjectType{}; } + OutputUnionType union_as_root() const { return OutputUnionType{}; } + OutputVarType null_as_root() const { return OutputVarType{}; } template @@ -54,6 +68,13 @@ class Writer { return OutputArrayType{}; } + OutputArrayType add_array_to_map(const std::string_view& _name, + const size_t _size, + OutputMapType* _parent) const { + (*archive_)(::cereal::make_size_tag(_size)); + return OutputArrayType{}; + } + OutputArrayType add_array_to_object(const std::string_view& _name, const size_t _size, OutputObjectType* _parent) const { @@ -61,23 +82,92 @@ class Writer { return OutputArrayType{}; } + OutputArrayType add_array_to_union(const size_t _index, const size_t _size, + OutputUnionType* _parent) const { + (*archive_)(::cereal::make_size_tag(_size)); + return OutputArrayType{}; + } + + OutputMapType add_map_to_array(const size_t _size, + OutputArrayType* _parent) const { + (*archive_)(::cereal::make_size_tag(_size)); + return OutputMapType{}; + } + + OutputMapType add_map_to_map(const std::string_view& _name, + const size_t _size, + OutputMapType* _parent) const { + (*archive_)(::cereal::make_size_tag(_size)); + return OutputMapType{}; + } + + OutputMapType add_map_to_object(const std::string_view& _name, + const size_t _size, + OutputObjectType* _parent) const { + (*archive_)(::cereal::make_size_tag(_size)); + return OutputMapType{}; + } + + OutputMapType add_map_to_union(const size_t _index, const size_t _size, + OutputUnionType* _parent) const { + (*archive_)(::cereal::make_size_tag(_size)); + return OutputMapType{}; + } + OutputObjectType add_object_to_array(const size_t _size, OutputArrayType* _parent) const { return OutputObjectType{}; } + OutputObjectType add_object_to_map(const std::string_view& _name, + const size_t _size, + OutputMapType* _parent) const { + return OutputObjectType{}; + } + OutputObjectType add_object_to_object(const std::string_view& _name, const size_t _size, OutputObjectType* _parent) const { return OutputObjectType{}; } + OutputObjectType add_object_to_union(const size_t _index, const size_t _size, + OutputUnionType* _parent) const { + return OutputObjectType{}; + } + + OutputUnionType add_union_to_array(OutputArrayType* _parent) const { + return OutputUnionType{}; + } + + OutputUnionType add_union_to_map(const std::string_view& _name, + OutputMapType* _parent) const { + return OutputUnionType{}; + } + + OutputUnionType add_union_to_object(const std::string_view& _name, + OutputObjectType* _parent) const { + return OutputUnionType{}; + } + + OutputUnionType add_union_to_union(const size_t _index, + OutputUnionType* _parent) const { + return OutputUnionType{}; + } + template OutputVarType add_value_to_array(const T& _var, OutputArrayType*) const { (*archive_)(_var); return OutputVarType{}; } + template + OutputVarType add_value_to_map(const std::string_view& _name, const T& _var, + OutputMapType* _parent) const { + (*archive_)(::cereal::make_nvp(_name.data(), _var)); + return OutputVarType{}; + } + template OutputVarType add_value_to_object(const std::string_view& _name, const T& _var, OutputObjectType*) const { @@ -85,23 +175,44 @@ class Writer { return OutputVarType{}; } + template + OutputVarType add_value_to_union(const size_t _index, const T& _var, + OutputUnionType* _parent) const { + (*archive_)(_var); + return OutputVarType{}; + } + OutputVarType add_null_to_array(OutputArrayType* _parent) const { return OutputVarType{}; } + OutputVarType add_null_to_map(const std::string_view& _name, + OutputMapType* _parent) const { + return OutputVarType{}; + } + OutputVarType add_null_to_object(const std::string_view& _name, OutputObjectType* _parent) const { return OutputVarType{}; } + OutputVarType add_null_to_union(const size_t _index, + OutputUnionType* _parent) const { + return OutputVarType{}; + } + void end_array(OutputArrayType* _arr) const noexcept {} + void end_map(OutputMapType* _map) const noexcept {} + void end_object(OutputObjectType* _obj) const noexcept {} private: CerealArchive* archive_; }; +static_assert(parsing::schemaful::IsSchemafulWriter); + } // namespace rfl::cereal #endif diff --git a/include/rfl/cereal/read.hpp b/include/rfl/cereal/read.hpp index 9d2e1d3e9..57ca4092b 100644 --- a/include/rfl/cereal/read.hpp +++ b/include/rfl/cereal/read.hpp @@ -32,7 +32,7 @@ Result> read( std::stringstream ss( std::string(reinterpret_cast(_bytes), _size)); ::cereal::PortableBinaryInputArchive archive(ss); - return read(archive); + return read(archive); } catch (std::exception& e) { return error(std::string("Cereal read error: ") + e.what()); } @@ -49,7 +49,7 @@ template auto read(std::istream& _stream) { try { ::cereal::BinaryInputArchive archive(_stream); - return read(archive); + return read(archive); } catch (std::exception& e) { return Result>( error(std::string("Cereal read error: ") + e.what())); diff --git a/include/rfl/cereal/write.hpp b/include/rfl/cereal/write.hpp index ca9d8aec0..f157f521e 100644 --- a/include/rfl/cereal/write.hpp +++ b/include/rfl/cereal/write.hpp @@ -26,8 +26,7 @@ std::vector write(const auto& _obj) { std::stringstream ss; { ::cereal::PortableBinaryOutputArchive archive(ss); - write, decltype(archive), Ps...>( - _obj, archive); + write, Ps...>(_obj, archive); } auto str = ss.str(); return std::vector(str.begin(), str.end()); @@ -37,8 +36,7 @@ std::vector write(const auto& _obj) { template std::ostream& write(const auto& _obj, std::ostream& _stream) { ::cereal::BinaryOutputArchive archive(_stream); - write, decltype(archive), Ps...>(_obj, - archive); + write, Ps...>(_obj, archive); return _stream; } diff --git a/tests/cereal/test_box.cpp b/tests/cereal/test_box.cpp index 06c44cb22..18a79cfbd 100644 --- a/tests/cereal/test_box.cpp +++ b/tests/cereal/test_box.cpp @@ -38,6 +38,7 @@ TEST(cereal, test_box) { const DecisionTree tree = DecisionTree{.leaf_or_node = std::move(node)}; - write_and_read(tree); + // TODO + // write_and_read(tree); } } // namespace test_box diff --git a/tests/cereal/test_enum.cpp b/tests/cereal/test_enum.cpp index 925200726..17bb88fac 100644 --- a/tests/cereal/test_enum.cpp +++ b/tests/cereal/test_enum.cpp @@ -16,7 +16,8 @@ struct Circle { }; TEST(cereal, test_enum) { - const auto circle = Circle{.radius = 5.0, .color = Color::green}; - write_and_read(circle); + // const auto circle = Circle{.radius = 5.0, .color = Color::green}; + // TODO + // write_and_read(circle); } } // namespace test_enum From 4d8be0e4f6fe692d7d535d54c75612f7cb0fac93 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 29 Mar 2026 16:02:30 +0200 Subject: [PATCH 03/42] More fixes --- include/rfl/cereal/Reader.hpp | 4 +- include/rfl/cereal/Writer.hpp | 2 +- include/rfl/parsing/ViewReader.hpp | 5 ++- include/rfl/parsing/ViewReaderWithDefault.hpp | 2 + ...ReaderWithDefaultAndStrippedFieldNames.hpp | 2 + .../ViewReaderWithStrippedFieldNames.hpp | 2 + tests/cereal/CMakeLists.txt | 2 +- tests/cereal/test_readme_example.cpp | 41 +++++++++---------- 8 files changed, 32 insertions(+), 28 deletions(-) diff --git a/include/rfl/cereal/Reader.hpp b/include/rfl/cereal/Reader.hpp index 7f01cb9b3..1b2b7fc49 100644 --- a/include/rfl/cereal/Reader.hpp +++ b/include/rfl/cereal/Reader.hpp @@ -122,9 +122,7 @@ struct Reader { std::optional read_object(const ObjectReader& _object_reader, const InputObjectType& _obj) const noexcept { try { - ::cereal::size_type size; - (*_obj.archive_)(::cereal::make_size_tag(size)); - for (::cereal::size_type i = 0; i < size; ++i) { + for (size_t i = 0; i < ObjectReader::size(); ++i) { _object_reader.read(i, InputVarType{_obj.archive_}); } return std::nullopt; diff --git a/include/rfl/cereal/Writer.hpp b/include/rfl/cereal/Writer.hpp index 4db05da5d..3503a0938 100644 --- a/include/rfl/cereal/Writer.hpp +++ b/include/rfl/cereal/Writer.hpp @@ -171,7 +171,7 @@ class Writer { template OutputVarType add_value_to_object(const std::string_view& _name, const T& _var, OutputObjectType*) const { - (*archive_)(::cereal::make_nvp(_name.data(), _var)); + (*archive_)(_var); return OutputVarType{}; } diff --git a/include/rfl/parsing/ViewReader.hpp b/include/rfl/parsing/ViewReader.hpp index 8bdbfbdd4..e7d491125 100644 --- a/include/rfl/parsing/ViewReader.hpp +++ b/include/rfl/parsing/ViewReader.hpp @@ -44,6 +44,8 @@ class ViewReader { std::make_integer_sequence()); } + static constexpr size_t size() { return size_; } + private: template static bool is_matching(const int _current_index) { @@ -141,8 +143,7 @@ class ViewReader { "sense, because schemaful formats cannot have extra fields."); if (!already_assigned) { std::stringstream stream; - stream << "Value named '" << _current_name_or_index - << "' not used."; + stream << "Value named '" << _current_name_or_index << "' not used."; _errors->emplace_back(Error(stream.str())); } } diff --git a/include/rfl/parsing/ViewReaderWithDefault.hpp b/include/rfl/parsing/ViewReaderWithDefault.hpp index 8ee2d79ed..331fb2807 100644 --- a/include/rfl/parsing/ViewReaderWithDefault.hpp +++ b/include/rfl/parsing/ViewReaderWithDefault.hpp @@ -40,6 +40,8 @@ class ViewReaderWithDefault { std::make_integer_sequence()); } + static constexpr size_t size() { return size_; } + private: template static void assign_if_field_matches(const R& _r, diff --git a/include/rfl/parsing/ViewReaderWithDefaultAndStrippedFieldNames.hpp b/include/rfl/parsing/ViewReaderWithDefaultAndStrippedFieldNames.hpp index 7b12a2d8e..e9f067982 100644 --- a/include/rfl/parsing/ViewReaderWithDefaultAndStrippedFieldNames.hpp +++ b/include/rfl/parsing/ViewReaderWithDefaultAndStrippedFieldNames.hpp @@ -49,6 +49,8 @@ class ViewReaderWithDefaultAndStrippedFieldNames { return std::nullopt; } + static constexpr size_t size() { return size_; } + private: template static void assign_if_field_is_field_i(const R& _r, const auto& _var, diff --git a/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp b/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp index 5787a1e0e..7cdde5be0 100644 --- a/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp +++ b/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp @@ -50,6 +50,8 @@ class ViewReaderWithStrippedFieldNames { return std::nullopt; } + static constexpr size_t size() { return size_; } + private: template static void assign_if_field_is_field_i(const R& _r, const auto& _var, diff --git a/tests/cereal/CMakeLists.txt b/tests/cereal/CMakeLists.txt index f6a8a2928..c95241875 100644 --- a/tests/cereal/CMakeLists.txt +++ b/tests/cereal/CMakeLists.txt @@ -1,6 +1,6 @@ project(reflect-cpp-cereal-tests) -file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "*.cpp") +file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "test_readme_example.cpp") add_executable( reflect-cpp-cereal-tests diff --git a/tests/cereal/test_readme_example.cpp b/tests/cereal/test_readme_example.cpp index 9375dced9..1da49c681 100644 --- a/tests/cereal/test_readme_example.cpp +++ b/tests/cereal/test_readme_example.cpp @@ -10,36 +10,35 @@ namespace test_readme_example { using Age = rfl::Validator, rfl::Maximum<130>>; struct Person { - rfl::Rename<"firstName", std::string> first_name; + Age age = 0; + /*rfl::Rename<"firstName", std::string> first_name; rfl::Rename<"lastName", std::string> last_name = "Simpson"; std::string town = "Springfield"; rfl::Timestamp<"%Y-%m-%d"> birthday; - Age age; - rfl::Email email; + rfl::Email email;*/ std::vector child; }; -TEST(cereal, test_readme_example) { - const auto bart = Person{.first_name = "Bart", - .birthday = "1987-04-19", - .age = 10, - .email = "bart@simpson.com"}; - - const auto lisa = Person{.first_name = "Lisa", - .birthday = "1987-04-19", - .age = 8, - .email = "lisa@simpson.com"}; +TEST(cereal, test_readme_example) { /* + const auto bart = Person{.age = 10, + .first_name = "Bart", + .birthday = "1987-04-19", - const auto maggie = Person{.first_name = "Maggie", - .birthday = "1987-04-19", - .age = 0, - .email = "maggie@simpson.com"}; + .email = "bart@simpson.com"}; - const auto homer = Person{.first_name = "Homer", + const auto lisa = Person{.first_name = "Lisa", .birthday = "1987-04-19", - .age = 45, - .email = "homer@simpson.com", - .child = std::vector({bart, lisa, maggie})}; + .email = "lisa@simpson.com"}; + + const auto maggie = Person{.first_name = "Maggie", + .birthday = "1987-04-19", + .email ="maggie@simpson.com"};*/ + + const auto homer = + Person{}; /*.first_name = "Homer", + .birthday = "1987-04-19", + .email = "homer@simpson.com", + .child = std::vector({bart, lisa, maggie})};*/ write_and_read(homer); } From 14af0f34116e423d88c4d04b40672fb6a645484e Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 29 Mar 2026 17:57:12 +0200 Subject: [PATCH 04/42] Added support for strings --- include/rfl/cereal/Reader.hpp | 1 + include/rfl/cereal/Writer.hpp | 1 + tests/cereal/test_readme_example.cpp | 40 ++++++++++++++-------------- tests/cereal/write_and_read.hpp | 3 +++ 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/include/rfl/cereal/Reader.hpp b/include/rfl/cereal/Reader.hpp index 1b2b7fc49..e913c1a11 100644 --- a/include/rfl/cereal/Reader.hpp +++ b/include/rfl/cereal/Reader.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include diff --git a/include/rfl/cereal/Writer.hpp b/include/rfl/cereal/Writer.hpp index 3503a0938..b4054ef3f 100644 --- a/include/rfl/cereal/Writer.hpp +++ b/include/rfl/cereal/Writer.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include diff --git a/tests/cereal/test_readme_example.cpp b/tests/cereal/test_readme_example.cpp index 1da49c681..6cc2baaae 100644 --- a/tests/cereal/test_readme_example.cpp +++ b/tests/cereal/test_readme_example.cpp @@ -10,35 +10,35 @@ namespace test_readme_example { using Age = rfl::Validator, rfl::Maximum<130>>; struct Person { - Age age = 0; - /*rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"firstName", std::string> first_name; rfl::Rename<"lastName", std::string> last_name = "Simpson"; std::string town = "Springfield"; rfl::Timestamp<"%Y-%m-%d"> birthday; - rfl::Email email;*/ + Age age; + rfl::Email email; std::vector child; }; -TEST(cereal, test_readme_example) { /* - const auto bart = Person{.age = 10, - .first_name = "Bart", - .birthday = "1987-04-19", - - .email = "bart@simpson.com"}; +TEST(cereal, test_readme_example) { + const auto bart = Person{.first_name = "Bart", + .birthday = "1987-04-19", + .age = 10, + .email = "bart@simpson.com"}; - const auto lisa = Person{.first_name = "Lisa", - .birthday = "1987-04-19", - .email = "lisa@simpson.com"}; + const auto lisa = Person{.first_name = "Lisa", + .birthday = "1987-04-19", + .age = 8, + .email = "lisa@simpson.com"}; - const auto maggie = Person{.first_name = "Maggie", - .birthday = "1987-04-19", - .email ="maggie@simpson.com"};*/ + const auto maggie = Person{.first_name = "Maggie", + .birthday = "1987-04-19", + .age = 0, + .email = "maggie@simpson.com"}; - const auto homer = - Person{}; /*.first_name = "Homer", - .birthday = "1987-04-19", - .email = "homer@simpson.com", - .child = std::vector({bart, lisa, maggie})};*/ + const auto homer = Person{.first_name = "Homer", + .birthday = "1987-04-19", + .email = "homer@simpson.com", + .child = std::vector({bart, lisa, maggie})}; write_and_read(homer); } diff --git a/tests/cereal/write_and_read.hpp b/tests/cereal/write_and_read.hpp index fc84a002a..1f41ad59d 100644 --- a/tests/cereal/write_and_read.hpp +++ b/tests/cereal/write_and_read.hpp @@ -4,6 +4,7 @@ #include #include +#include template void write_and_read(const auto& _struct) { @@ -14,5 +15,7 @@ void write_and_read(const auto& _struct) { << res.error().what(); const auto serialized2 = rfl::cereal::write(res.value()); EXPECT_EQ(serialized1, serialized2); + EXPECT_EQ(rfl::json::write(_struct), + rfl::json::write(res.value())); } #endif From 2373ced8a2445222ef5246a8f8aba1ecf4ba9061 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 30 Mar 2026 04:38:20 +0200 Subject: [PATCH 05/42] Fixed union --- include/rfl/cereal/Reader.hpp | 8 ++------ include/rfl/cereal/Writer.hpp | 6 ++++++ include/rfl/cereal/write.hpp | 2 +- tests/cereal/CMakeLists.txt | 2 +- tests/cereal/test_map.cpp | 14 +++++--------- tests/cereal/test_tuple.cpp | 1 - 6 files changed, 15 insertions(+), 18 deletions(-) diff --git a/include/rfl/cereal/Reader.hpp b/include/rfl/cereal/Reader.hpp index e913c1a11..e76c03b4f 100644 --- a/include/rfl/cereal/Reader.hpp +++ b/include/rfl/cereal/Reader.hpp @@ -107,11 +107,7 @@ struct Reader { for (::cereal::size_type i = 0; i < size; ++i) { std::string key; (*_map.archive_)(key); - const auto err = _map_reader.read(std::string_view(key), - InputVarType{_map.archive_}); - if (err) { - return err; - } + _map_reader.read(std::string_view(key), InputVarType{_map.archive_}); } return std::nullopt; } catch (std::exception& e) { @@ -135,7 +131,7 @@ struct Reader { template rfl::Result read_union(const InputUnionType& _union) const noexcept { try { - std::int32_t index; + size_t index; (*_union.archive_)(index); return UnionReader::read(*this, index, InputVarType{_union.archive_}); } catch (std::exception& e) { diff --git a/include/rfl/cereal/Writer.hpp b/include/rfl/cereal/Writer.hpp index b4054ef3f..bdb618e9c 100644 --- a/include/rfl/cereal/Writer.hpp +++ b/include/rfl/cereal/Writer.hpp @@ -85,6 +85,7 @@ class Writer { OutputArrayType add_array_to_union(const size_t _index, const size_t _size, OutputUnionType* _parent) const { + (*archive_)(_index); (*archive_)(::cereal::make_size_tag(_size)); return OutputArrayType{}; } @@ -111,6 +112,7 @@ class Writer { OutputMapType add_map_to_union(const size_t _index, const size_t _size, OutputUnionType* _parent) const { + (*archive_)(_index); (*archive_)(::cereal::make_size_tag(_size)); return OutputMapType{}; } @@ -134,6 +136,7 @@ class Writer { OutputObjectType add_object_to_union(const size_t _index, const size_t _size, OutputUnionType* _parent) const { + (*archive_)(_index); return OutputObjectType{}; } @@ -153,6 +156,7 @@ class Writer { OutputUnionType add_union_to_union(const size_t _index, OutputUnionType* _parent) const { + (*archive_)(_index); return OutputUnionType{}; } @@ -179,6 +183,7 @@ class Writer { template OutputVarType add_value_to_union(const size_t _index, const T& _var, OutputUnionType* _parent) const { + (*archive_)(_index); (*archive_)(_var); return OutputVarType{}; } @@ -199,6 +204,7 @@ class Writer { OutputVarType add_null_to_union(const size_t _index, OutputUnionType* _parent) const { + (*archive_)(_index); return OutputVarType{}; } diff --git a/include/rfl/cereal/write.hpp b/include/rfl/cereal/write.hpp index f157f521e..d219031ee 100644 --- a/include/rfl/cereal/write.hpp +++ b/include/rfl/cereal/write.hpp @@ -35,7 +35,7 @@ std::vector write(const auto& _obj) { /// Writes Cereal binary format into an ostream. template std::ostream& write(const auto& _obj, std::ostream& _stream) { - ::cereal::BinaryOutputArchive archive(_stream); + ::cereal::PortableBinaryOutputArchive archive(_stream); write, Ps...>(_obj, archive); return _stream; } diff --git a/tests/cereal/CMakeLists.txt b/tests/cereal/CMakeLists.txt index c95241875..f6a8a2928 100644 --- a/tests/cereal/CMakeLists.txt +++ b/tests/cereal/CMakeLists.txt @@ -1,6 +1,6 @@ project(reflect-cpp-cereal-tests) -file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "test_readme_example.cpp") +file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "*.cpp") add_executable( reflect-cpp-cereal-tests diff --git a/tests/cereal/test_map.cpp b/tests/cereal/test_map.cpp index b9a1dfee0..3b8aad9c1 100644 --- a/tests/cereal/test_map.cpp +++ b/tests/cereal/test_map.cpp @@ -8,6 +8,7 @@ #include "write_and_read.hpp" namespace test_map { + struct Person { rfl::Rename<"firstName", std::string> first_name; rfl::Rename<"lastName", std::string> last_name = "Simpson"; @@ -15,16 +16,11 @@ struct Person { }; TEST(cereal, test_map) { - auto bart = Person{.first_name = "Bart"}; - - auto lisa = Person{.first_name = "Lisa"}; - - auto maggie = Person{.first_name = "Maggie"}; + auto children = std::make_unique>(); - auto children = std::make_unique>( - std::map({{"Bart", std::move(bart)}, - {"Lisa", std::move(lisa)}, - {"Maggie", std::move(maggie)}})); + children->insert(std::make_pair("Bart", Person{.first_name = "Bart"})); + children->insert(std::make_pair("Lisa", Person{.first_name = "Lisa"})); + children->insert(std::make_pair("Maggie", Person{.first_name = "Maggie"})); const auto homer = Person{.first_name = "Homer", .children = std::move(children)}; diff --git a/tests/cereal/test_tuple.cpp b/tests/cereal/test_tuple.cpp index 55459a2dc..c7dfcfe24 100644 --- a/tests/cereal/test_tuple.cpp +++ b/tests/cereal/test_tuple.cpp @@ -10,7 +10,6 @@ namespace test_tuple { TEST(cereal, test_tuple) { - using TupleType = std::tuple; const auto my_tuple = std::make_tuple(42, std::string("Hello"), true, 3.14); write_and_read(my_tuple); } From 82f19134b319f056e132eb1f2819b77217e9f1ad Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 30 Mar 2026 06:40:19 +0200 Subject: [PATCH 06/42] Minor fixes --- include/rfl/cereal/Reader.hpp | 8 ++++---- include/rfl/cereal/Writer.hpp | 13 ++++++++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/include/rfl/cereal/Reader.hpp b/include/rfl/cereal/Reader.hpp index e76c03b4f..e332f48ba 100644 --- a/include/rfl/cereal/Reader.hpp +++ b/include/rfl/cereal/Reader.hpp @@ -84,9 +84,9 @@ struct Reader { std::optional read_array(const ArrayReader& _array_reader, const InputArrayType& _arr) const noexcept { try { - ::cereal::size_type size; + size_t size; (*_arr.archive_)(::cereal::make_size_tag(size)); - for (::cereal::size_type i = 0; i < size; ++i) { + for (size_t i = 0; i < size; ++i) { const auto err = _array_reader.read(InputVarType{_arr.archive_}); if (err) { return err; @@ -102,9 +102,9 @@ struct Reader { std::optional read_map(const MapReader& _map_reader, const InputMapType& _map) const noexcept { try { - ::cereal::size_type size; + size_t size; (*_map.archive_)(::cereal::make_size_tag(size)); - for (::cereal::size_type i = 0; i < size; ++i) { + for (size_t i = 0; i < size; ++i) { std::string key; (*_map.archive_)(key); _map_reader.read(std::string_view(key), InputVarType{_map.archive_}); diff --git a/include/rfl/cereal/Writer.hpp b/include/rfl/cereal/Writer.hpp index bdb618e9c..baa866635 100644 --- a/include/rfl/cereal/Writer.hpp +++ b/include/rfl/cereal/Writer.hpp @@ -72,6 +72,7 @@ class Writer { OutputArrayType add_array_to_map(const std::string_view& _name, const size_t _size, OutputMapType* _parent) const { + add_string_view(_name); (*archive_)(::cereal::make_size_tag(_size)); return OutputArrayType{}; } @@ -99,6 +100,7 @@ class Writer { OutputMapType add_map_to_map(const std::string_view& _name, const size_t _size, OutputMapType* _parent) const { + (*archive_)(std::string(_name)); (*archive_)(::cereal::make_size_tag(_size)); return OutputMapType{}; } @@ -125,6 +127,7 @@ class Writer { OutputObjectType add_object_to_map(const std::string_view& _name, const size_t _size, OutputMapType* _parent) const { + add_string_view(_name); return OutputObjectType{}; } @@ -146,6 +149,7 @@ class Writer { OutputUnionType add_union_to_map(const std::string_view& _name, OutputMapType* _parent) const { + add_string_view(_name); return OutputUnionType{}; } @@ -169,7 +173,7 @@ class Writer { template OutputVarType add_value_to_map(const std::string_view& _name, const T& _var, OutputMapType* _parent) const { - (*archive_)(::cereal::make_nvp(_name.data(), _var)); + add_string_view(_name); return OutputVarType{}; } @@ -194,6 +198,7 @@ class Writer { OutputVarType add_null_to_map(const std::string_view& _name, OutputMapType* _parent) const { + add_string_view(_name); return OutputVarType{}; } @@ -214,6 +219,12 @@ class Writer { void end_object(OutputObjectType* _obj) const noexcept {} + private: + void add_string_view(const std::string_view& _str) const { + (*archive_)(::cereal::make_size_tag(_str.size())); + (*archive_)(::cereal::binary_data(_str.data(), _str.size())); + } + private: CerealArchive* archive_; }; From adb818cd0c4b24a60bf57856f7b8309d4a8088cb Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 30 Mar 2026 06:40:24 +0200 Subject: [PATCH 07/42] More tests --- tests/cereal/test_add_struct_name.cpp | 45 ++++++++++++++ tests/cereal/test_bytestring.cpp | 18 ++++++ tests/cereal/test_custom_class1.cpp | 35 +++++++++++ tests/cereal/test_custom_class3.cpp | 62 ++++++++++++++++++++ tests/cereal/test_custom_class4.cpp | 68 ++++++++++++++++++++++ tests/cereal/test_default_values.cpp | 26 +++++++++ tests/cereal/test_deque.cpp | 26 +++++++++ tests/cereal/test_enum.cpp | 5 +- tests/cereal/test_field_variant.cpp | 33 +++++++++++ tests/cereal/test_field_variant_std.cpp | 33 +++++++++++ tests/cereal/test_flag_enum.cpp | 32 ++++++++++ tests/cereal/test_flag_enum_with_int.cpp | 31 ++++++++++ tests/cereal/test_flatten.cpp | 31 ++++++++++ tests/cereal/test_flatten_anonymous.cpp | 32 ++++++++++ tests/cereal/test_forward_list.cpp | 26 +++++++++ tests/cereal/test_literal.cpp | 24 ++++++++ tests/cereal/test_literal_map.cpp | 24 ++++++++ tests/cereal/test_map2.cpp | 27 +++++++++ tests/cereal/test_monster_example.cpp | 59 +++++++++++++++++++ tests/cereal/test_optionals_in_vectors.cpp | 29 +++++++++ tests/cereal/test_readme_example2.cpp | 20 +++++++ tests/cereal/test_readme_example3.cpp | 30 ++++++++++ tests/cereal/test_ref.cpp | 41 +++++++++++++ tests/cereal/test_rfl_tuple.cpp | 32 ++++++++++ tests/cereal/test_rfl_variant.cpp | 30 ++++++++++ tests/cereal/test_set.cpp | 23 ++++++++ tests/cereal/test_shared_ptr.cpp | 28 +++++++++ tests/cereal/test_size.cpp | 36 ++++++++++++ tests/cereal/test_string_map.cpp | 24 ++++++++ tests/cereal/test_unique_ptr2.cpp | 40 +++++++++++++ tests/cereal/test_variants_in_vectors.cpp | 31 ++++++++++ tests/cereal/test_wstring.cpp | 19 ++++++ 32 files changed, 1017 insertions(+), 3 deletions(-) create mode 100644 tests/cereal/test_add_struct_name.cpp create mode 100644 tests/cereal/test_bytestring.cpp create mode 100644 tests/cereal/test_custom_class1.cpp create mode 100644 tests/cereal/test_custom_class3.cpp create mode 100644 tests/cereal/test_custom_class4.cpp create mode 100644 tests/cereal/test_default_values.cpp create mode 100644 tests/cereal/test_deque.cpp create mode 100644 tests/cereal/test_field_variant.cpp create mode 100644 tests/cereal/test_field_variant_std.cpp create mode 100644 tests/cereal/test_flag_enum.cpp create mode 100644 tests/cereal/test_flag_enum_with_int.cpp create mode 100644 tests/cereal/test_flatten.cpp create mode 100644 tests/cereal/test_flatten_anonymous.cpp create mode 100644 tests/cereal/test_forward_list.cpp create mode 100644 tests/cereal/test_literal.cpp create mode 100644 tests/cereal/test_literal_map.cpp create mode 100644 tests/cereal/test_map2.cpp create mode 100644 tests/cereal/test_monster_example.cpp create mode 100644 tests/cereal/test_optionals_in_vectors.cpp create mode 100644 tests/cereal/test_readme_example2.cpp create mode 100644 tests/cereal/test_readme_example3.cpp create mode 100644 tests/cereal/test_ref.cpp create mode 100644 tests/cereal/test_rfl_tuple.cpp create mode 100644 tests/cereal/test_rfl_variant.cpp create mode 100644 tests/cereal/test_set.cpp create mode 100644 tests/cereal/test_shared_ptr.cpp create mode 100644 tests/cereal/test_size.cpp create mode 100644 tests/cereal/test_string_map.cpp create mode 100644 tests/cereal/test_unique_ptr2.cpp create mode 100644 tests/cereal/test_variants_in_vectors.cpp create mode 100644 tests/cereal/test_wstring.cpp diff --git a/tests/cereal/test_add_struct_name.cpp b/tests/cereal/test_add_struct_name.cpp new file mode 100644 index 000000000..03d2a41d8 --- /dev/null +++ b/tests/cereal/test_add_struct_name.cpp @@ -0,0 +1,45 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_add_struct_name { + +using Age = rfl::Validator, rfl::Maximum<130>>; + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::string town = "Springfield"; + rfl::Timestamp<"%Y-%m-%d"> birthday; + Age age; + rfl::Email email; + std::vector children; +}; + +TEST(cereal, test_add_struct_name) { + /*const auto bart = Person{.first_name = "Bart", + .birthday = "1987-04-19", + .age = 10, + .email = "bart@simpson.com"}; + + const auto lisa = Person{.first_name = "Lisa", + .birthday = "1987-04-19", + .age = 8, + .email = "lisa@simpson.com"}; + + const auto maggie = Person{.first_name = "Maggie", + .birthday = "1987-04-19", + .age = 0, + .email = "maggie@simpson.com"}; + + const auto homer = + Person{.first_name = "Homer", + .birthday = "1987-04-19", + .age = 45, + .email = "homer@simpson.com", + .children = std::vector({bart, lisa, maggie})}; + + write_and_read>(homer);*/ +} +} // namespace test_add_struct_name diff --git a/tests/cereal/test_bytestring.cpp b/tests/cereal/test_bytestring.cpp new file mode 100644 index 000000000..6f86404a1 --- /dev/null +++ b/tests/cereal/test_bytestring.cpp @@ -0,0 +1,18 @@ +#include + +#include "write_and_read.hpp" + +namespace test_bytestring { + +struct TestStruct { + rfl::Bytestring bytestring; +}; + +TEST(cereal, test_bytestring) { + /*const auto test = + TestStruct{.bytestring = rfl::Bytestring({std::byte{13}, std::byte{14}, + std::byte{15}, std::byte{16}})}; + + write_and_read(test);*/ +} +} // namespace test_bytestring diff --git a/tests/cereal/test_custom_class1.cpp b/tests/cereal/test_custom_class1.cpp new file mode 100644 index 000000000..ec1c4aefe --- /dev/null +++ b/tests/cereal/test_custom_class1.cpp @@ -0,0 +1,35 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_custom_class1 { + +struct Person { + struct PersonImpl { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::vector children; + }; + + using ReflectionType = PersonImpl; + + Person(const PersonImpl& _impl) : impl(_impl) {} + + Person(const std::string& _first_name) + : impl(PersonImpl{.first_name = _first_name}) {} + + const ReflectionType& reflection() const { return impl; }; + + private: + PersonImpl impl; +}; + +TEST(cereal, test_custom_class1) { + const auto bart = Person("Bart"); + + write_and_read(bart); +} +} // namespace test_custom_class1 diff --git a/tests/cereal/test_custom_class3.cpp b/tests/cereal/test_custom_class3.cpp new file mode 100644 index 000000000..1876297d3 --- /dev/null +++ b/tests/cereal/test_custom_class3.cpp @@ -0,0 +1,62 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_custom_class3 { + +struct Person { + Person(const std::string& _first_name, const std::string& _last_name, + const int _age) + : first_name_(_first_name), last_name_(_last_name), age_(_age) {} + + const auto& first_name() const { return first_name_; } + + const auto& last_name() const { return last_name_; } + + auto age() const { return age_; } + + private: + std::string first_name_; + std::string last_name_; + int age_; +}; + +struct PersonImpl { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name; + int age; + + static PersonImpl from_class(const Person& _p) noexcept { + return PersonImpl{.first_name = _p.first_name(), + .last_name = _p.last_name(), + .age = _p.age()}; + } + + Person to_class() const { return Person(first_name(), last_name(), age); } +}; +} // namespace test_custom_class3 + +namespace rfl { +namespace parsing { + +template +struct Parser + : public CustomParser {}; + +} // namespace parsing +} // namespace rfl + +namespace test_custom_class3 { + +TEST(cereal, test_custom_class3) { + const auto bart = Person("Bart", "Simpson", 10); + + write_and_read(bart); +} + +} // namespace test_custom_class3 diff --git a/tests/cereal/test_custom_class4.cpp b/tests/cereal/test_custom_class4.cpp new file mode 100644 index 000000000..97ddd04d7 --- /dev/null +++ b/tests/cereal/test_custom_class4.cpp @@ -0,0 +1,68 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_custom_class4 { + +struct Person { + Person(const std::string& _first_name, + const rfl::Box& _last_name, int _age) + : first_name_(_first_name), + last_name_(rfl::make_box(*_last_name)), + age_(_age) {} + + const auto& first_name() const { return first_name_; } + + const auto& last_name() const { return last_name_; } + + auto age() const { return age_; } + + private: + std::string first_name_; + rfl::Box last_name_; + int age_; +}; + +struct PersonImpl { + rfl::Field<"firstName", std::string> first_name; + rfl::Field<"lastName", rfl::Box> last_name; + rfl::Field<"age", int> age; + + static PersonImpl from_class(const Person& _p) noexcept { + return PersonImpl{.first_name = _p.first_name(), + .last_name = rfl::make_box(*_p.last_name()), + .age = _p.age()}; + } + + Person to_class() const { + return Person(first_name.value(), std::move(last_name.value()), + age.value()); + } +}; + +} // namespace test_custom_class4 + +namespace rfl { +namespace parsing { + +template +struct Parser + : public CustomParser {}; + +} // namespace parsing +} // namespace rfl + +namespace test_custom_class4 { + +TEST(cereal, test_custom_class4) { + const auto bart = test_custom_class4::Person( + "Bart", rfl::make_box("Simpson"), 10); + + write_and_read(bart); +} +} // namespace test_custom_class4 diff --git a/tests/cereal/test_default_values.cpp b/tests/cereal/test_default_values.cpp new file mode 100644 index 000000000..3fb3c9160 --- /dev/null +++ b/tests/cereal/test_default_values.cpp @@ -0,0 +1,26 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_default_values { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::vector children; +}; + +TEST(cereal, test_default_values) { + const auto bart = Person{.first_name = "Bart"}; + const auto lisa = Person{.first_name = "Lisa"}; + const auto maggie = Person{.first_name = "Maggie"}; + const auto homer = + Person{.first_name = "Homer", + .children = std::vector({bart, lisa, maggie})}; + + write_and_read(homer); +} +} // namespace test_default_values diff --git a/tests/cereal/test_deque.cpp b/tests/cereal/test_deque.cpp new file mode 100644 index 000000000..a0bcc9474 --- /dev/null +++ b/tests/cereal/test_deque.cpp @@ -0,0 +1,26 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_deque { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::unique_ptr> children; +}; + +TEST(cereal, test_default_values) { + auto children = std::make_unique>(); + children->emplace_back(Person{.first_name = "Bart"}); + children->emplace_back(Person{.first_name = "Lisa"}); + children->emplace_back(Person{.first_name = "Maggie"}); + + const auto homer = + Person{.first_name = "Homer", .children = std::move(children)}; + + write_and_read(homer); + +} +} // namespace test_deque diff --git a/tests/cereal/test_enum.cpp b/tests/cereal/test_enum.cpp index 17bb88fac..925200726 100644 --- a/tests/cereal/test_enum.cpp +++ b/tests/cereal/test_enum.cpp @@ -16,8 +16,7 @@ struct Circle { }; TEST(cereal, test_enum) { - // const auto circle = Circle{.radius = 5.0, .color = Color::green}; - // TODO - // write_and_read(circle); + const auto circle = Circle{.radius = 5.0, .color = Color::green}; + write_and_read(circle); } } // namespace test_enum diff --git a/tests/cereal/test_field_variant.cpp b/tests/cereal/test_field_variant.cpp new file mode 100644 index 000000000..5c0d3d9fd --- /dev/null +++ b/tests/cereal/test_field_variant.cpp @@ -0,0 +1,33 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_field_variant { + +struct Circle { + double radius; +}; + +struct Rectangle { + double height; + double width; +}; + +struct Square { + double width; +}; + +struct Shapes { + rfl::Variant, rfl::Field<"rectangle", Rectangle>, + rfl::Field<"square", rfl::Box>> + root; +}; + +TEST(cereal, test_field_variant_std) { + const auto r = + Shapes{rfl::make_field<"rectangle">(Rectangle{.height = 10, .width = 5})}; + + write_and_read(r); +} +} // namespace test_field_variant diff --git a/tests/cereal/test_field_variant_std.cpp b/tests/cereal/test_field_variant_std.cpp new file mode 100644 index 000000000..4932cb24d --- /dev/null +++ b/tests/cereal/test_field_variant_std.cpp @@ -0,0 +1,33 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_field_variant_std { + +struct Circle { + double radius; +}; + +struct Rectangle { + double height; + double width; +}; + +struct Square { + double width; +}; + +struct Shapes { + std::variant, rfl::Field<"rectangle", Rectangle>, + rfl::Field<"square", rfl::Box>> + root; +}; + +TEST(cereal, test_field_variant_std) { + const auto r = + Shapes{rfl::make_field<"rectangle">(Rectangle{.height = 10, .width = 5})}; + + write_and_read(r); +} +} // namespace test_field_variant_std diff --git a/tests/cereal/test_flag_enum.cpp b/tests/cereal/test_flag_enum.cpp new file mode 100644 index 000000000..aa7d13965 --- /dev/null +++ b/tests/cereal/test_flag_enum.cpp @@ -0,0 +1,32 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_flag_enum { + +enum class Color { + red = 256, + green = 512, + blue = 1024, + yellow = 2048, + orange = (256 | 2048) // red + yellow = orange +}; + +inline Color operator|(Color c1, Color c2) { + return static_cast(static_cast(c1) | static_cast(c2)); +} + +struct Circle { + float radius; + Color color; +}; + +TEST(cereal, test_flag_enum) { + const auto circle = + Circle{.radius = 2.0, .color = Color::blue | Color::orange}; + + write_and_read(circle); +} + +} // namespace test_flag_enum diff --git a/tests/cereal/test_flag_enum_with_int.cpp b/tests/cereal/test_flag_enum_with_int.cpp new file mode 100644 index 000000000..3605e8cbd --- /dev/null +++ b/tests/cereal/test_flag_enum_with_int.cpp @@ -0,0 +1,31 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_flag_enum_with_int { + +enum class Color { + red = 256, + green = 512, + blue = 1024, + yellow = 2048, + orange = (256 | 2048) // red + yellow = orange +}; + +inline Color operator|(Color c1, Color c2) { + return static_cast(static_cast(c1) | static_cast(c2)); +} + +struct Circle { + float radius; + Color color; +}; + +TEST(cereal, test_flag_enum_with_int) { + const auto circle = Circle{.radius = 2.0, .color = static_cast(10000)}; + + write_and_read(circle); +} + +} // namespace test_flag_enum_with_int diff --git a/tests/cereal/test_flatten.cpp b/tests/cereal/test_flatten.cpp new file mode 100644 index 000000000..50323a912 --- /dev/null +++ b/tests/cereal/test_flatten.cpp @@ -0,0 +1,31 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_flatten { + +struct Person { + rfl::Field<"firstName", std::string> first_name; + rfl::Field<"lastName", rfl::Box> last_name; + rfl::Field<"age", int> age; +}; + +struct Employee { + rfl::Flatten person; + rfl::Field<"employer", rfl::Box> employer; + rfl::Field<"salary", float> salary; +}; + +TEST(cereal, test_flatten) { + const auto employee = Employee{ + .person = Person{.first_name = "Homer", + .last_name = rfl::make_box("Simpson"), + .age = 45}, + .employer = rfl::make_box("Mr. Burns"), + .salary = 60000.0}; + + write_and_read(employee); +} +} // namespace test_flatten diff --git a/tests/cereal/test_flatten_anonymous.cpp b/tests/cereal/test_flatten_anonymous.cpp new file mode 100644 index 000000000..ec8ea1182 --- /dev/null +++ b/tests/cereal/test_flatten_anonymous.cpp @@ -0,0 +1,32 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_flatten_anonymous { + +struct Person { + std::string first_name; + rfl::Box last_name; + int age; +}; + +struct Employee { + rfl::Flatten person; + rfl::Box employer; + float salary; +}; + +TEST(cereal, test_flatten_anonymous) { + const auto employee = Employee{ + .person = Person{.first_name = "Homer", + .last_name = rfl::make_box("Simpson"), + .age = 45}, + .employer = rfl::make_box("Mr. Burns"), + .salary = 60000.0}; + + write_and_read(employee); +} + +} // namespace test_flatten_anonymous diff --git a/tests/cereal/test_forward_list.cpp b/tests/cereal/test_forward_list.cpp new file mode 100644 index 000000000..5b1696cbb --- /dev/null +++ b/tests/cereal/test_forward_list.cpp @@ -0,0 +1,26 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_forward_list { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::unique_ptr> children; +}; + +TEST(cereal, test_forward_list) { + auto children = std::make_unique>(); + children->emplace_front(Person{.first_name = "Maggie"}); + children->emplace_front(Person{.first_name = "Lisa"}); + children->emplace_front(Person{.first_name = "Bart"}); + + const auto homer = + Person{.first_name = "Homer", .children = std::move(children)}; + + write_and_read(homer); + +} +} // namespace test_forward_list diff --git a/tests/cereal/test_literal.cpp b/tests/cereal/test_literal.cpp new file mode 100644 index 000000000..807df0685 --- /dev/null +++ b/tests/cereal/test_literal.cpp @@ -0,0 +1,24 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_literal { + +using FirstName = rfl::Literal<"Homer", "Marge", "Bart", "Lisa", "Maggie">; +using LastName = rfl::Literal<"Simpson">; + +struct Person { + rfl::Rename<"firstName", FirstName> first_name; + rfl::Rename<"lastName", LastName> last_name; + std::vector children; +}; + +TEST(cereal, test_literal) { + /*const auto bart = Person{.first_name = FirstName::make<"Bart">()}; + + write_and_read(bart);*/ +} +} // namespace test_literal diff --git a/tests/cereal/test_literal_map.cpp b/tests/cereal/test_literal_map.cpp new file mode 100644 index 000000000..f3baa931c --- /dev/null +++ b/tests/cereal/test_literal_map.cpp @@ -0,0 +1,24 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_literal_map { + +using FieldName = rfl::Literal<"firstName", "lastName">; + +struct RootStruct { + std::map> root; +}; + +TEST(cereal, test_literal_map) { + RootStruct homer; + homer.root.insert(std::make_pair(FieldName::make<"firstName">(), + std::make_unique("Homer"))); + homer.root.insert(std::make_pair(FieldName::make<"lastName">(), + std::make_unique("Simpson"))); + write_and_read(homer); +} +} // namespace test_literal_map diff --git a/tests/cereal/test_map2.cpp b/tests/cereal/test_map2.cpp new file mode 100644 index 000000000..dddc2b456 --- /dev/null +++ b/tests/cereal/test_map2.cpp @@ -0,0 +1,27 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_map2 { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::map children; +}; + +TEST(cereal, test_map2) { + auto children = std::map(); + children.insert(std::make_pair("Bart", Person{.first_name = "Bart"})); + children.insert(std::make_pair("Lisa", Person{.first_name = "Lisa"})); + children.insert(std::make_pair("Maggie", Person{.first_name = "Maggie"})); + + const auto homer = + Person{.first_name = "Homer", .children = std::move(children)}; + + write_and_read(homer); +} +} // namespace test_map2 diff --git a/tests/cereal/test_monster_example.cpp b/tests/cereal/test_monster_example.cpp new file mode 100644 index 000000000..3b911b285 --- /dev/null +++ b/tests/cereal/test_monster_example.cpp @@ -0,0 +1,59 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_monster_example { + +using Color = rfl::Literal<"Red", "Green", "Blue">; + +struct Weapon { + std::string name; + short damage; +}; + +using Equipment = + rfl::Variant, rfl::Field<"None", int>>; + +struct Vec3 { + float x; + float y; + float z; +}; + +struct Monster { + Vec3 pos; + short mana = 150; + short hp = 100; + std::string name; + bool friendly = false; + std::vector inventory; + Color color = Color::make<"Blue">(); + std::vector weapons; + Equipment equipped; + std::vector path; +}; + +TEST(cereal, test_monster_example) { + /*const auto sword = Weapon{.name = "Sword", .damage = 3}; + const auto axe = Weapon{.name = "Axe", .damage = 5}; + + const auto weapons = std::vector({sword, axe}); + + const auto position = Vec3{1.0f, 2.0f, 3.0f}; + + const auto inventory = std::vector({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + + const auto orc = Monster{.pos = position, + .mana = 150, + .hp = 80, + .name = "MyMonster", + .inventory = inventory, + .color = Color::make<"Red">(), + .weapons = weapons, + .equipped = rfl::make_field<"weapon">(axe)}; + + write_and_read(orc);*/ +} +} // namespace test_monster_example diff --git a/tests/cereal/test_optionals_in_vectors.cpp b/tests/cereal/test_optionals_in_vectors.cpp new file mode 100644 index 000000000..70c952a33 --- /dev/null +++ b/tests/cereal/test_optionals_in_vectors.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_optionals_in_vectors { + +struct Person { + std::string first_name; + std::string last_name = "Simpson"; + std::vector> children; +}; + +TEST(cereal, test_optionals_in_vectors) { + const std::optional bart = Person{.first_name = "Bart"}; + + const std::optional lisa = Person{.first_name = "Lisa"}; + + const std::optional maggie = Person{.first_name = "Maggie"}; + + const auto homer = Person{.first_name = "Homer", + .children = std::vector>( + {bart, lisa, maggie, std::nullopt})}; + + write_and_read(homer); +} +} // namespace test_optionals_in_vectors diff --git a/tests/cereal/test_readme_example2.cpp b/tests/cereal/test_readme_example2.cpp new file mode 100644 index 000000000..b912b2be1 --- /dev/null +++ b/tests/cereal/test_readme_example2.cpp @@ -0,0 +1,20 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_readme_example2 { + +struct Person { + std::string first_name; + std::string last_name; + int age; +}; + +TEST(cereal, test_readme_example2) { + const auto homer = + Person{.first_name = "Homer", .last_name = "Simpson", .age = 45}; + + write_and_read(homer); +} +} // namespace test_readme_example2 diff --git a/tests/cereal/test_readme_example3.cpp b/tests/cereal/test_readme_example3.cpp new file mode 100644 index 000000000..4c9873c22 --- /dev/null +++ b/tests/cereal/test_readme_example3.cpp @@ -0,0 +1,30 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_readme_example3 { + +struct Person { + std::string first_name; + std::string last_name; + rfl::Timestamp<"%Y-%m-%d"> birthday; + std::vector children; +}; + +TEST(cereal, test_readme_example3) { + const auto bart = Person{.first_name = "Bart", .birthday = "1987-04-19"}; + + const auto lisa = Person{.first_name = "Lisa", .birthday = "1987-04-19"}; + + const auto maggie = Person{.first_name = "Maggie", .birthday = "1987-04-19"}; + + const auto homer = + Person{.first_name = "Homer", + .birthday = "1987-04-19", + .children = std::vector({bart, lisa, maggie})}; + + write_and_read(homer); +} +} // namespace test_readme_example3 diff --git a/tests/cereal/test_ref.cpp b/tests/cereal/test_ref.cpp new file mode 100644 index 000000000..0e4a2f2ec --- /dev/null +++ b/tests/cereal/test_ref.cpp @@ -0,0 +1,41 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_ref { + +struct DecisionTree { + struct Leaf { + using Tag = rfl::Literal<"Leaf">; + double value; + }; + + struct Node { + using Tag = rfl::Literal<"Node">; + rfl::Rename<"criticalValue", double> critical_value; + rfl::Ref lesser; + rfl::Ref greater; + }; + + using LeafOrNode = rfl::TaggedUnion<"type", Leaf, Node>; + + rfl::Field<"leafOrNode", LeafOrNode> leaf_or_node; +}; + +TEST(cereal, test_ref) { + const auto leaf1 = DecisionTree::Leaf{.value = 3.0}; + + const auto leaf2 = DecisionTree::Leaf{.value = 5.0}; + + auto node = DecisionTree::Node{ + .critical_value = 10.0, + .lesser = rfl::make_ref(DecisionTree{leaf1}), + .greater = rfl::make_ref(DecisionTree{leaf2})}; + + const DecisionTree tree{.leaf_or_node = std::move(node)}; + + write_and_read(tree); + +} +} // namespace test_ref diff --git a/tests/cereal/test_rfl_tuple.cpp b/tests/cereal/test_rfl_tuple.cpp new file mode 100644 index 000000000..5729a867e --- /dev/null +++ b/tests/cereal/test_rfl_tuple.cpp @@ -0,0 +1,32 @@ + +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_rfl_tuple { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::unique_ptr> children = nullptr; +}; + +TEST(cereal, test_rfl_tuple) { + auto bart = Person{.first_name = "Bart"}; + + auto lisa = Person{.first_name = "Lisa"}; + + auto maggie = Person{.first_name = "Maggie"}; + + const auto homer = + Person{.first_name = "Homer", + .children = std::make_unique>( + rfl::Tuple{ + std::move(bart), std::move(lisa), std::move(maggie)})}; + + write_and_read(homer); +} +} // namespace test_rfl_tuple diff --git a/tests/cereal/test_rfl_variant.cpp b/tests/cereal/test_rfl_variant.cpp new file mode 100644 index 000000000..4be257c94 --- /dev/null +++ b/tests/cereal/test_rfl_variant.cpp @@ -0,0 +1,30 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_variant { + +struct Circle { + double radius; +}; + +struct Rectangle { + double height; + double width; +}; + +struct Square { + double width; +}; + +struct Shapes { + rfl::Variant> root; +}; + +TEST(cereal, test_rfl_variant) { + const auto r = Shapes{Rectangle{.height = 10, .width = 5}}; + + write_and_read(r); +} +} // namespace test_variant diff --git a/tests/cereal/test_set.cpp b/tests/cereal/test_set.cpp new file mode 100644 index 000000000..bf8aa128b --- /dev/null +++ b/tests/cereal/test_set.cpp @@ -0,0 +1,23 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_set { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::unique_ptr> children; +}; + +TEST(cereal, test_set) { + auto children = std::make_unique>( + std::set({"Bart", "Lisa", "Maggie"})); + + const auto homer = + Person{.first_name = "Homer", .children = std::move(children)}; + + write_and_read(homer); +} +} // namespace test_set diff --git a/tests/cereal/test_shared_ptr.cpp b/tests/cereal/test_shared_ptr.cpp new file mode 100644 index 000000000..f9a870a04 --- /dev/null +++ b/tests/cereal/test_shared_ptr.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_shared_ptr { + +struct Person { + std::string first_name; + std::string last_name = "Simpson"; + std::shared_ptr> children; +}; + +TEST(cereal, test_shared_ptr) { + auto children = std::make_shared>(); + children->emplace_back(Person{.first_name = "Bart"}); + children->emplace_back(Person{.first_name = "Lisa"}); + children->emplace_back(Person{.first_name = "Maggie"}); + + const auto homer = + Person{.first_name = "Homer", .children = std::move(children)}; + + write_and_read(homer); +} +} // namespace test_shared_ptr diff --git a/tests/cereal/test_size.cpp b/tests/cereal/test_size.cpp new file mode 100644 index 000000000..4d6741465 --- /dev/null +++ b/tests/cereal/test_size.cpp @@ -0,0 +1,36 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_size { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name; + rfl::Timestamp<"%Y-%m-%d"> birthday; + rfl::Validator, + rfl::Size, rfl::EqualTo<3>>>> + children; +}; + +TEST(cereal, test_size) { + const auto bart = Person{ + .first_name = "Bart", .last_name = "Simpson", .birthday = "1987-04-19"}; + + const auto lisa = Person{ + .first_name = "Lisa", .last_name = "Simpson", .birthday = "1987-04-19"}; + + const auto maggie = Person{ + .first_name = "Maggie", .last_name = "Simpson", .birthday = "1987-04-19"}; + + const auto homer = + Person{.first_name = "Homer", + .last_name = "Simpson", + .birthday = "1987-04-19", + .children = std::vector({bart, lisa, maggie})}; + + write_and_read(homer); +} +} // namespace test_size diff --git a/tests/cereal/test_string_map.cpp b/tests/cereal/test_string_map.cpp new file mode 100644 index 000000000..b0e16ec77 --- /dev/null +++ b/tests/cereal/test_string_map.cpp @@ -0,0 +1,24 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_string_map { + +struct RootStruct { + std::map> root; +}; + +TEST(cereal, test_string_map) { + RootStruct homer; + homer.root.insert( + std::make_pair("firstName", std::make_unique("Homer"))); + homer.root.insert( + std::make_pair("lastName", std::make_unique("Simpson"))); + + write_and_read(homer); +} +} // namespace test_string_map diff --git a/tests/cereal/test_unique_ptr2.cpp b/tests/cereal/test_unique_ptr2.cpp new file mode 100644 index 000000000..31d5d72db --- /dev/null +++ b/tests/cereal/test_unique_ptr2.cpp @@ -0,0 +1,40 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_unique_ptr2 { + +struct DecisionTree { + struct Leaf { + using Tag = rfl::Literal<"Leaf">; + double value; + }; + + struct Node { + using Tag = rfl::Literal<"Node">; + rfl::Rename<"criticalValue", double> critical_value; + std::unique_ptr lesser; + std::unique_ptr greater; + }; + + using LeafOrNode = rfl::TaggedUnion<"type", Leaf, Node>; + + rfl::Field<"leafOrNode", LeafOrNode> leaf_or_node; +}; + +TEST(cereal, test_unique_ptr2) { + auto leaf1 = DecisionTree::Leaf{.value = 3.0}; + + auto leaf2 = DecisionTree::Leaf{.value = 5.0}; + + auto node = DecisionTree::Node{ + .critical_value = 10.0, + .lesser = std::make_unique(DecisionTree{leaf1}), + .greater = std::make_unique(DecisionTree{leaf2})}; + + const DecisionTree tree{.leaf_or_node = std::move(node)}; + + write_and_read(tree); +} +} // namespace test_unique_ptr2 diff --git a/tests/cereal/test_variants_in_vectors.cpp b/tests/cereal/test_variants_in_vectors.cpp new file mode 100644 index 000000000..0f1a3d891 --- /dev/null +++ b/tests/cereal/test_variants_in_vectors.cpp @@ -0,0 +1,31 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_variants_in_vectors { + +struct Person { + std::string first_name; + std::string last_name = "Simpson"; + std::vector> children; +}; + +TEST(cereal, test_variants_in_vectors) { + const std::variant bart = Person{.first_name = "Bart"}; + + const std::variant lisa = Person{.first_name = "Lisa"}; + + const std::variant maggie = + Person{.first_name = "Maggie"}; + + const auto homer = + Person{.first_name = "Homer", + .children = std::vector>( + {bart, lisa, maggie, "Unknown"})}; + + write_and_read(homer); +} +} // namespace test_variants_in_vectors diff --git a/tests/cereal/test_wstring.cpp b/tests/cereal/test_wstring.cpp new file mode 100644 index 000000000..aef3725bf --- /dev/null +++ b/tests/cereal/test_wstring.cpp @@ -0,0 +1,19 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +struct TestStruct { + std::string theNormalString; + std::wstring theWiderString; +}; + +namespace test_wstring { +TEST(cereal, test_wstring) { + const auto test = TestStruct{.theNormalString = "The normal string", + .theWiderString = L"The wider string"}; + + write_and_read(test); +} +} // namespace test_wstring From be0423b7c5585dabe9caec6e222cb02da349aba0 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 30 Mar 2026 06:53:09 +0200 Subject: [PATCH 08/42] Added support for literals --- include/rfl/cereal/Reader.hpp | 13 ++++++++++--- include/rfl/cereal/Writer.hpp | 18 +++++++++++++++--- tests/cereal/test_add_struct_name.cpp | 4 ++-- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/include/rfl/cereal/Reader.hpp b/include/rfl/cereal/Reader.hpp index e332f48ba..7ef684ee7 100644 --- a/include/rfl/cereal/Reader.hpp +++ b/include/rfl/cereal/Reader.hpp @@ -11,6 +11,7 @@ #include "../Result.hpp" #include "../always_false.hpp" +#include "../internal/is_literal.hpp" #include "../parsing/schemaful/IsSchemafulReader.hpp" namespace rfl::cereal { @@ -53,9 +54,15 @@ struct Reader { template rfl::Result to_basic_type(const InputVarType& _var) const noexcept { try { - T value; - (*_var.archive_)(value); - return value; + if constexpr (internal::is_literal_v) { + std::string str; + (*_var.archive_)(str); + return std::remove_cvref_t::from_string(str); + } else { + T value; + (*_var.archive_)(value); + return value; + } } catch (std::exception& e) { return error(std::string("Cereal read error: ") + e.what()); } diff --git a/include/rfl/cereal/Writer.hpp b/include/rfl/cereal/Writer.hpp index baa866635..7d493b66a 100644 --- a/include/rfl/cereal/Writer.hpp +++ b/include/rfl/cereal/Writer.hpp @@ -11,6 +11,7 @@ #include "../always_false.hpp" #include "../common.hpp" +#include "../internal/is_literal.hpp" #include "../parsing/schemaful/IsSchemafulWriter.hpp" namespace rfl::cereal { @@ -166,7 +167,7 @@ class Writer { template OutputVarType add_value_to_array(const T& _var, OutputArrayType*) const { - (*archive_)(_var); + add_value(_var); return OutputVarType{}; } @@ -174,13 +175,14 @@ class Writer { OutputVarType add_value_to_map(const std::string_view& _name, const T& _var, OutputMapType* _parent) const { add_string_view(_name); + add_value(_var); return OutputVarType{}; } template OutputVarType add_value_to_object(const std::string_view& _name, const T& _var, OutputObjectType*) const { - (*archive_)(_var); + add_value(_var); return OutputVarType{}; } @@ -188,7 +190,7 @@ class Writer { OutputVarType add_value_to_union(const size_t _index, const T& _var, OutputUnionType* _parent) const { (*archive_)(_index); - (*archive_)(_var); + add_value(_var); return OutputVarType{}; } @@ -225,6 +227,16 @@ class Writer { (*archive_)(::cereal::binary_data(_str.data(), _str.size())); } + template + void add_value(const T& _var) const noexcept { + using Type = std::remove_cvref_t; + if constexpr (internal::is_literal_v) { + add_value(_var.str()); + } else { + (*archive_)(_var); + } + } + private: CerealArchive* archive_; }; diff --git a/tests/cereal/test_add_struct_name.cpp b/tests/cereal/test_add_struct_name.cpp index 03d2a41d8..1f8e5ab13 100644 --- a/tests/cereal/test_add_struct_name.cpp +++ b/tests/cereal/test_add_struct_name.cpp @@ -18,7 +18,7 @@ struct Person { }; TEST(cereal, test_add_struct_name) { - /*const auto bart = Person{.first_name = "Bart", + const auto bart = Person{.first_name = "Bart", .birthday = "1987-04-19", .age = 10, .email = "bart@simpson.com"}; @@ -40,6 +40,6 @@ TEST(cereal, test_add_struct_name) { .email = "homer@simpson.com", .children = std::vector({bart, lisa, maggie})}; - write_and_read>(homer);*/ + write_and_read>(homer); } } // namespace test_add_struct_name From 45e9ab1746fced3ee0362a4585373a6156e12f16 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 30 Mar 2026 07:06:47 +0200 Subject: [PATCH 09/42] Added support for bytestrings --- include/rfl/cereal/Reader.hpp | 13 +++++++++++++ include/rfl/cereal/Writer.hpp | 8 ++++++++ tests/cereal/test_bytestring.cpp | 4 ++-- tests/cereal/test_literal.cpp | 4 ++-- tests/cereal/test_monster_example.cpp | 4 ++-- tests/cereal/write_and_read.hpp | 3 --- 6 files changed, 27 insertions(+), 9 deletions(-) diff --git a/include/rfl/cereal/Reader.hpp b/include/rfl/cereal/Reader.hpp index 7ef684ee7..3b6ec8e70 100644 --- a/include/rfl/cereal/Reader.hpp +++ b/include/rfl/cereal/Reader.hpp @@ -9,7 +9,9 @@ #include #include +#include "../Bytestring.hpp" #include "../Result.hpp" +#include "../Vectorstring.hpp" #include "../always_false.hpp" #include "../internal/is_literal.hpp" #include "../parsing/schemaful/IsSchemafulReader.hpp" @@ -58,6 +60,17 @@ struct Reader { std::string str; (*_var.archive_)(str); return std::remove_cvref_t::from_string(str); + + } else if constexpr (std::is_same, + rfl::Bytestring>() || + std::is_same, + rfl::Vectorstring>()) { + size_t size; + (*_var.archive_)(::cereal::make_size_tag(size)); + std::remove_cvref_t result(size); + (*_var.archive_)(::cereal::binary_data(result.data(), size)); + return result; + } else { T value; (*_var.archive_)(value); diff --git a/include/rfl/cereal/Writer.hpp b/include/rfl/cereal/Writer.hpp index 7d493b66a..1bf456d40 100644 --- a/include/rfl/cereal/Writer.hpp +++ b/include/rfl/cereal/Writer.hpp @@ -9,6 +9,8 @@ #include #include +#include "../Bytestring.hpp" +#include "../Vectorstring.hpp" #include "../always_false.hpp" #include "../common.hpp" #include "../internal/is_literal.hpp" @@ -232,6 +234,12 @@ class Writer { using Type = std::remove_cvref_t; if constexpr (internal::is_literal_v) { add_value(_var.str()); + + } else if constexpr (std::is_same() || + std::is_same()) { + (*archive_)(::cereal::make_size_tag(_var.size())); + (*archive_)(::cereal::binary_data(_var.data(), _var.size())); + } else { (*archive_)(_var); } diff --git a/tests/cereal/test_bytestring.cpp b/tests/cereal/test_bytestring.cpp index 6f86404a1..8a897ca12 100644 --- a/tests/cereal/test_bytestring.cpp +++ b/tests/cereal/test_bytestring.cpp @@ -9,10 +9,10 @@ struct TestStruct { }; TEST(cereal, test_bytestring) { - /*const auto test = + const auto test = TestStruct{.bytestring = rfl::Bytestring({std::byte{13}, std::byte{14}, std::byte{15}, std::byte{16}})}; - write_and_read(test);*/ + write_and_read(test); } } // namespace test_bytestring diff --git a/tests/cereal/test_literal.cpp b/tests/cereal/test_literal.cpp index 807df0685..873bece8e 100644 --- a/tests/cereal/test_literal.cpp +++ b/tests/cereal/test_literal.cpp @@ -17,8 +17,8 @@ struct Person { }; TEST(cereal, test_literal) { - /*const auto bart = Person{.first_name = FirstName::make<"Bart">()}; + const auto bart = Person{.first_name = FirstName::make<"Bart">()}; - write_and_read(bart);*/ + write_and_read(bart); } } // namespace test_literal diff --git a/tests/cereal/test_monster_example.cpp b/tests/cereal/test_monster_example.cpp index 3b911b285..3bf66f849 100644 --- a/tests/cereal/test_monster_example.cpp +++ b/tests/cereal/test_monster_example.cpp @@ -36,7 +36,7 @@ struct Monster { }; TEST(cereal, test_monster_example) { - /*const auto sword = Weapon{.name = "Sword", .damage = 3}; + const auto sword = Weapon{.name = "Sword", .damage = 3}; const auto axe = Weapon{.name = "Axe", .damage = 5}; const auto weapons = std::vector({sword, axe}); @@ -54,6 +54,6 @@ TEST(cereal, test_monster_example) { .weapons = weapons, .equipped = rfl::make_field<"weapon">(axe)}; - write_and_read(orc);*/ + write_and_read(orc); } } // namespace test_monster_example diff --git a/tests/cereal/write_and_read.hpp b/tests/cereal/write_and_read.hpp index 1f41ad59d..fc84a002a 100644 --- a/tests/cereal/write_and_read.hpp +++ b/tests/cereal/write_and_read.hpp @@ -4,7 +4,6 @@ #include #include -#include template void write_and_read(const auto& _struct) { @@ -15,7 +14,5 @@ void write_and_read(const auto& _struct) { << res.error().what(); const auto serialized2 = rfl::cereal::write(res.value()); EXPECT_EQ(serialized1, serialized2); - EXPECT_EQ(rfl::json::write(_struct), - rfl::json::write(res.value())); } #endif From 9fb0caf395f18516259c1386841b61341c133553 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 30 Mar 2026 07:12:19 +0200 Subject: [PATCH 10/42] Added tests for generic --- tests/cereal/test_generic.cpp | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/cereal/test_generic.cpp diff --git a/tests/cereal/test_generic.cpp b/tests/cereal/test_generic.cpp new file mode 100644 index 000000000..1dfd789d1 --- /dev/null +++ b/tests/cereal/test_generic.cpp @@ -0,0 +1,33 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_generic { + +TEST(cereal, test_generic) { + auto bart = rfl::Generic::Object(); + bart["first_name"] = "Bart"; + bart["last_name"] = "Simpson"; + bart["age"] = 10; + + auto lisa = rfl::Generic::Object(); + lisa["first_name"] = "Lisa"; + lisa["last_name"] = "Simpson"; + lisa["age"] = 8; + + auto maggie = rfl::Generic::Object(); + maggie["first_name"] = "Lisa"; + maggie["last_name"] = "Simpson"; + maggie["age"] = 0; + + auto homer = rfl::Generic::Object(); + homer["first_name"] = "Lisa"; + homer["last_name"] = "Simpson"; + homer["age"] = 45; + homer["children"] = rfl::Generic::Array({bart, lisa, maggie}); + + write_and_read(homer); +} +} // namespace test_generic From 4541411f941cf3f58c67d9ca0e83dcf0f570e1f9 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 30 Mar 2026 07:27:23 +0200 Subject: [PATCH 11/42] Added cereal to the benchmarks --- benchmarks/all/canada_read.cpp | 12 ++++++++++++ benchmarks/all/canada_write.cpp | 12 ++++++++++++ benchmarks/all/licenses_read.cpp | 12 ++++++++++++ benchmarks/all/licenses_write.cpp | 12 ++++++++++++ benchmarks/all/person_read.cpp | 12 ++++++++++++ benchmarks/all/person_write.cpp | 12 ++++++++++++ 6 files changed, 72 insertions(+) diff --git a/benchmarks/all/canada_read.cpp b/benchmarks/all/canada_read.cpp index 9449a74b4..e848ffea4 100644 --- a/benchmarks/all/canada_read.cpp +++ b/benchmarks/all/canada_read.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -104,6 +105,17 @@ static void BM_canada_read_reflect_cpp_cbor_without_field_names( } BENCHMARK(BM_canada_read_reflect_cpp_cbor_without_field_names); +static void BM_canada_read_reflect_cpp_cereal(benchmark::State &state) { + const auto data = rfl::cereal::write(load_data()); + for (auto _ : state) { + const auto res = rfl::cereal::read(data); + if (!res) { + std::cout << res.error().what() << std::endl; + } + } +} +BENCHMARK(BM_canada_read_reflect_cpp_cereal); + static void BM_canada_read_reflect_cpp_flexbuf(benchmark::State &state) { const auto data = rfl::flexbuf::write(load_data()); for (auto _ : state) { diff --git a/benchmarks/all/canada_write.cpp b/benchmarks/all/canada_write.cpp index 80a817b07..3b7005c0f 100644 --- a/benchmarks/all/canada_write.cpp +++ b/benchmarks/all/canada_write.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -103,6 +104,17 @@ static void BM_canada_write_reflect_cpp_cbor_without_field_names( } BENCHMARK(BM_canada_write_reflect_cpp_cbor_without_field_names); +static void BM_canada_write_reflect_cpp_cereal(benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::cereal::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_canada_write_reflect_cpp_cereal); + static void BM_canada_write_reflect_cpp_flexbuf_without_field_names( benchmark::State &state) { const auto data = load_data(); diff --git a/benchmarks/all/licenses_read.cpp b/benchmarks/all/licenses_read.cpp index 2bbb3a7a1..289582520 100644 --- a/benchmarks/all/licenses_read.cpp +++ b/benchmarks/all/licenses_read.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -120,6 +121,17 @@ static void BM_licenses_read_reflect_cpp_cbor_without_field_names( } BENCHMARK(BM_licenses_read_reflect_cpp_cbor_without_field_names); +static void BM_licenses_read_reflect_cpp_cereal(benchmark::State &state) { + const auto data = rfl::cereal::write(load_data()); + for (auto _ : state) { + const auto res = rfl::cereal::read(data); + if (!res) { + std::cout << res.error().what() << std::endl; + } + } +} +BENCHMARK(BM_licenses_read_reflect_cpp_cereal); + static void BM_licenses_read_reflect_cpp_flexbuf(benchmark::State &state) { const auto data = rfl::flexbuf::write(load_data()); for (auto _ : state) { diff --git a/benchmarks/all/licenses_write.cpp b/benchmarks/all/licenses_write.cpp index db9f9a87a..74e74746e 100644 --- a/benchmarks/all/licenses_write.cpp +++ b/benchmarks/all/licenses_write.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -120,6 +121,17 @@ static void BM_licenses_write_reflect_cpp_cbor_without_field_names( } BENCHMARK(BM_licenses_write_reflect_cpp_cbor_without_field_names); +static void BM_licenses_write_reflect_cpp_cereal(benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::cereal::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_licenses_write_reflect_cpp_cereal); + static void BM_licenses_write_reflect_cpp_flexbuf(benchmark::State &state) { const auto data = load_data(); for (auto _ : state) { diff --git a/benchmarks/all/person_read.cpp b/benchmarks/all/person_read.cpp index 9db2e769b..80e79d263 100644 --- a/benchmarks/all/person_read.cpp +++ b/benchmarks/all/person_read.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -98,6 +99,17 @@ static void BM_person_read_reflect_cpp_cbor_without_field_names( } BENCHMARK(BM_person_read_reflect_cpp_cbor_without_field_names); +static void BM_person_read_reflect_cpp_cereal(benchmark::State &state) { + const auto data = rfl::cereal::write(load_data()); + for (auto _ : state) { + const auto res = rfl::cereal::read(data); + if (!res) { + std::cout << res.error().what() << std::endl; + } + } +} +BENCHMARK(BM_person_read_reflect_cpp_cereal); + static void BM_person_read_reflect_cpp_flexbuf(benchmark::State &state) { const auto data = rfl::flexbuf::write(load_data()); for (auto _ : state) { diff --git a/benchmarks/all/person_write.cpp b/benchmarks/all/person_write.cpp index 02fafda8d..3e2f51311 100644 --- a/benchmarks/all/person_write.cpp +++ b/benchmarks/all/person_write.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -99,6 +100,17 @@ static void BM_person_write_reflect_cpp_cbor_without_field_names( } BENCHMARK(BM_person_write_reflect_cpp_cbor_without_field_names); +static void BM_person_write_reflect_cpp_cereal(benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::cereal::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_person_write_reflect_cpp_cereal); + static void BM_person_write_reflect_cpp_flexbuf(benchmark::State &state) { const auto data = load_data(); for (auto _ : state) { From 01fc4e7fa0053a8e054de9a31146c2fa33586042 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 30 Mar 2026 07:45:00 +0200 Subject: [PATCH 12/42] Outsourced to src files --- include/rfl/cereal/Reader.hpp | 20 +--- include/rfl/cereal/Writer.hpp | 129 ++++++----------------- src/reflectcpp_cereal.cpp | 3 +- src/rfl/cereal/Reader.cpp | 55 ++++++++++ src/rfl/cereal/Writer.cpp | 190 ++++++++++++++++++++++++++++++++++ 5 files changed, 282 insertions(+), 115 deletions(-) create mode 100644 src/rfl/cereal/Reader.cpp create mode 100644 src/rfl/cereal/Writer.cpp diff --git a/include/rfl/cereal/Reader.hpp b/include/rfl/cereal/Reader.hpp index 3b6ec8e70..dd8420a3a 100644 --- a/include/rfl/cereal/Reader.hpp +++ b/include/rfl/cereal/Reader.hpp @@ -49,9 +49,7 @@ struct Reader { static constexpr bool has_custom_constructor = (requires(InputVarType var) { T::from_cereal_obj(var); }); - bool is_empty(const InputVarType& _var) const noexcept { - return _var.archive_ == nullptr; - } + bool is_empty(const InputVarType& _var) const noexcept; template rfl::Result to_basic_type(const InputVarType& _var) const noexcept { @@ -82,23 +80,15 @@ struct Reader { } rfl::Result to_array( - const InputVarType& _var) const noexcept { - return InputArrayType{_var.archive_}; - } + const InputVarType& _var) const noexcept; rfl::Result to_object( - const InputVarType& _var) const noexcept { - return InputObjectType{_var.archive_}; - } + const InputVarType& _var) const noexcept; - rfl::Result to_map(const InputVarType& _var) const noexcept { - return InputMapType{_var.archive_}; - } + rfl::Result to_map(const InputVarType& _var) const noexcept; rfl::Result to_union( - const InputVarType& _var) const noexcept { - return InputUnionType{_var.archive_}; - } + const InputVarType& _var) const noexcept; template std::optional read_array(const ArrayReader& _array_reader, diff --git a/include/rfl/cereal/Writer.hpp b/include/rfl/cereal/Writer.hpp index 1bf456d40..84c3d4a0a 100644 --- a/include/rfl/cereal/Writer.hpp +++ b/include/rfl/cereal/Writer.hpp @@ -38,27 +38,19 @@ class Writer { using OutputUnionType = CerealOutputUnion; using OutputVarType = CerealOutputVar; - Writer(CerealArchive* _archive) : archive_(_archive) {} + Writer(CerealArchive* _archive); ~Writer() = default; - OutputArrayType array_as_root(const size_t _size) const { - (*archive_)(::cereal::make_size_tag(_size)); - return OutputArrayType{}; - } + OutputArrayType array_as_root(const size_t _size) const; - OutputMapType map_as_root(const size_t _size) const { - (*archive_)(::cereal::make_size_tag(_size)); - return OutputMapType{}; - } + OutputMapType map_as_root(const size_t _size) const; - OutputObjectType object_as_root(const size_t _size) const { - return OutputObjectType{}; - } + OutputObjectType object_as_root(const size_t _size) const; - OutputUnionType union_as_root() const { return OutputUnionType{}; } + OutputUnionType union_as_root() const; - OutputVarType null_as_root() const { return OutputVarType{}; } + OutputVarType null_as_root() const; template OutputVarType value_as_root(const T& _var) const { @@ -67,105 +59,57 @@ class Writer { } OutputArrayType add_array_to_array(const size_t _size, - OutputArrayType* _parent) const { - (*archive_)(::cereal::make_size_tag(_size)); - return OutputArrayType{}; - } + OutputArrayType* _parent) const; OutputArrayType add_array_to_map(const std::string_view& _name, const size_t _size, - OutputMapType* _parent) const { - add_string_view(_name); - (*archive_)(::cereal::make_size_tag(_size)); - return OutputArrayType{}; - } + OutputMapType* _parent) const; OutputArrayType add_array_to_object(const std::string_view& _name, const size_t _size, - OutputObjectType* _parent) const { - (*archive_)(::cereal::make_size_tag(_size)); - return OutputArrayType{}; - } + OutputObjectType* _parent) const; OutputArrayType add_array_to_union(const size_t _index, const size_t _size, - OutputUnionType* _parent) const { - (*archive_)(_index); - (*archive_)(::cereal::make_size_tag(_size)); - return OutputArrayType{}; - } + OutputUnionType* _parent) const; OutputMapType add_map_to_array(const size_t _size, - OutputArrayType* _parent) const { - (*archive_)(::cereal::make_size_tag(_size)); - return OutputMapType{}; - } + OutputArrayType* _parent) const; OutputMapType add_map_to_map(const std::string_view& _name, const size_t _size, - OutputMapType* _parent) const { - (*archive_)(std::string(_name)); - (*archive_)(::cereal::make_size_tag(_size)); - return OutputMapType{}; - } + OutputMapType* _parent) const; OutputMapType add_map_to_object(const std::string_view& _name, const size_t _size, - OutputObjectType* _parent) const { - (*archive_)(::cereal::make_size_tag(_size)); - return OutputMapType{}; - } + OutputObjectType* _parent) const; OutputMapType add_map_to_union(const size_t _index, const size_t _size, - OutputUnionType* _parent) const { - (*archive_)(_index); - (*archive_)(::cereal::make_size_tag(_size)); - return OutputMapType{}; - } + OutputUnionType* _parent) const; OutputObjectType add_object_to_array(const size_t _size, - OutputArrayType* _parent) const { - return OutputObjectType{}; - } + OutputArrayType* _parent) const; OutputObjectType add_object_to_map(const std::string_view& _name, const size_t _size, - OutputMapType* _parent) const { - add_string_view(_name); - return OutputObjectType{}; - } + OutputMapType* _parent) const; OutputObjectType add_object_to_object(const std::string_view& _name, const size_t _size, - OutputObjectType* _parent) const { - return OutputObjectType{}; - } + OutputObjectType* _parent) const; OutputObjectType add_object_to_union(const size_t _index, const size_t _size, - OutputUnionType* _parent) const { - (*archive_)(_index); - return OutputObjectType{}; - } + OutputUnionType* _parent) const; - OutputUnionType add_union_to_array(OutputArrayType* _parent) const { - return OutputUnionType{}; - } + OutputUnionType add_union_to_array(OutputArrayType* _parent) const; OutputUnionType add_union_to_map(const std::string_view& _name, - OutputMapType* _parent) const { - add_string_view(_name); - return OutputUnionType{}; - } + OutputMapType* _parent) const; OutputUnionType add_union_to_object(const std::string_view& _name, - OutputObjectType* _parent) const { - return OutputUnionType{}; - } + OutputObjectType* _parent) const; OutputUnionType add_union_to_union(const size_t _index, - OutputUnionType* _parent) const { - (*archive_)(_index); - return OutputUnionType{}; - } + OutputUnionType* _parent) const; template OutputVarType add_value_to_array(const T& _var, OutputArrayType*) const { @@ -196,38 +140,25 @@ class Writer { return OutputVarType{}; } - OutputVarType add_null_to_array(OutputArrayType* _parent) const { - return OutputVarType{}; - } + OutputVarType add_null_to_array(OutputArrayType* _parent) const; OutputVarType add_null_to_map(const std::string_view& _name, - OutputMapType* _parent) const { - add_string_view(_name); - return OutputVarType{}; - } + OutputMapType* _parent) const; OutputVarType add_null_to_object(const std::string_view& _name, - OutputObjectType* _parent) const { - return OutputVarType{}; - } + OutputObjectType* _parent) const; OutputVarType add_null_to_union(const size_t _index, - OutputUnionType* _parent) const { - (*archive_)(_index); - return OutputVarType{}; - } + OutputUnionType* _parent) const; - void end_array(OutputArrayType* _arr) const noexcept {} + void end_array(OutputArrayType* _arr) const noexcept; - void end_map(OutputMapType* _map) const noexcept {} + void end_map(OutputMapType* _map) const noexcept; - void end_object(OutputObjectType* _obj) const noexcept {} + void end_object(OutputObjectType* _obj) const noexcept; private: - void add_string_view(const std::string_view& _str) const { - (*archive_)(::cereal::make_size_tag(_str.size())); - (*archive_)(::cereal::binary_data(_str.data(), _str.size())); - } + void add_string_view(const std::string_view& _str) const; template void add_value(const T& _var) const noexcept { diff --git a/src/reflectcpp_cereal.cpp b/src/reflectcpp_cereal.cpp index ec1ff8498..2d49d72e8 100644 --- a/src/reflectcpp_cereal.cpp +++ b/src/reflectcpp_cereal.cpp @@ -29,4 +29,5 @@ SOFTWARE. // Also, this speeds up compile time, compared to multiple separate .cpp files // compilation. -// Cereal is header-only, so this file is minimal +#include "rfl/cereal/Reader.cpp" +#include "rfl/cereal/Writer.cpp" diff --git a/src/rfl/cereal/Reader.cpp b/src/rfl/cereal/Reader.cpp new file mode 100644 index 000000000..e78d5c712 --- /dev/null +++ b/src/rfl/cereal/Reader.cpp @@ -0,0 +1,55 @@ +/* + +MIT License + +Copyright (c) 2023-2024 Code17 GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "rfl/cereal/Reader.hpp" + +namespace rfl::cereal { + +bool Reader::is_empty(const InputVarType& _var) const noexcept { + return _var.archive_ == nullptr; +} + +rfl::Result Reader::to_array( + const InputVarType& _var) const noexcept { + return InputArrayType{_var.archive_}; +} + +rfl::Result Reader::to_object( + const InputVarType& _var) const noexcept { + return InputObjectType{_var.archive_}; +} + +rfl::Result Reader::to_map( + const InputVarType& _var) const noexcept { + return InputMapType{_var.archive_}; +} + +rfl::Result Reader::to_union( + const InputVarType& _var) const noexcept { + return InputUnionType{_var.archive_}; +} + +} // namespace rfl::cereal diff --git a/src/rfl/cereal/Writer.cpp b/src/rfl/cereal/Writer.cpp new file mode 100644 index 000000000..8e64bb1a0 --- /dev/null +++ b/src/rfl/cereal/Writer.cpp @@ -0,0 +1,190 @@ +/* + +MIT License + +Copyright (c) 2023-2024 Code17 GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "rfl/cereal/Writer.hpp" + +namespace rfl::cereal { + +Writer::Writer(CerealArchive* _archive) : archive_(_archive) {} + +Writer::OutputArrayType Writer::array_as_root(const size_t _size) const { + (*archive_)(::cereal::make_size_tag(_size)); + return OutputArrayType{}; +} + +Writer::OutputMapType Writer::map_as_root(const size_t _size) const { + (*archive_)(::cereal::make_size_tag(_size)); + return OutputMapType{}; +} + +Writer::OutputObjectType Writer::object_as_root(const size_t _size) const { + return OutputObjectType{}; +} + +Writer::OutputUnionType Writer::union_as_root() const { + return OutputUnionType{}; +} + +Writer::OutputVarType Writer::null_as_root() const { + return OutputVarType{}; +} + +Writer::OutputArrayType Writer::add_array_to_array( + const size_t _size, OutputArrayType* _parent) const { + (*archive_)(::cereal::make_size_tag(_size)); + return OutputArrayType{}; +} + +Writer::OutputArrayType Writer::add_array_to_map( + const std::string_view& _name, const size_t _size, + OutputMapType* _parent) const { + add_string_view(_name); + (*archive_)(::cereal::make_size_tag(_size)); + return OutputArrayType{}; +} + +Writer::OutputArrayType Writer::add_array_to_object( + const std::string_view& _name, const size_t _size, + OutputObjectType* _parent) const { + (*archive_)(::cereal::make_size_tag(_size)); + return OutputArrayType{}; +} + +Writer::OutputArrayType Writer::add_array_to_union( + const size_t _index, const size_t _size, OutputUnionType* _parent) const { + (*archive_)(_index); + (*archive_)(::cereal::make_size_tag(_size)); + return OutputArrayType{}; +} + +Writer::OutputMapType Writer::add_map_to_array( + const size_t _size, OutputArrayType* _parent) const { + (*archive_)(::cereal::make_size_tag(_size)); + return OutputMapType{}; +} + +Writer::OutputMapType Writer::add_map_to_map( + const std::string_view& _name, const size_t _size, + OutputMapType* _parent) const { + (*archive_)(std::string(_name)); + (*archive_)(::cereal::make_size_tag(_size)); + return OutputMapType{}; +} + +Writer::OutputMapType Writer::add_map_to_object( + const std::string_view& _name, const size_t _size, + OutputObjectType* _parent) const { + (*archive_)(::cereal::make_size_tag(_size)); + return OutputMapType{}; +} + +Writer::OutputMapType Writer::add_map_to_union( + const size_t _index, const size_t _size, OutputUnionType* _parent) const { + (*archive_)(_index); + (*archive_)(::cereal::make_size_tag(_size)); + return OutputMapType{}; +} + +Writer::OutputObjectType Writer::add_object_to_array( + const size_t _size, OutputArrayType* _parent) const { + return OutputObjectType{}; +} + +Writer::OutputObjectType Writer::add_object_to_map( + const std::string_view& _name, const size_t _size, + OutputMapType* _parent) const { + add_string_view(_name); + return OutputObjectType{}; +} + +Writer::OutputObjectType Writer::add_object_to_object( + const std::string_view& _name, const size_t _size, + OutputObjectType* _parent) const { + return OutputObjectType{}; +} + +Writer::OutputObjectType Writer::add_object_to_union( + const size_t _index, const size_t _size, OutputUnionType* _parent) const { + (*archive_)(_index); + return OutputObjectType{}; +} + +Writer::OutputUnionType Writer::add_union_to_array( + OutputArrayType* _parent) const { + return OutputUnionType{}; +} + +Writer::OutputUnionType Writer::add_union_to_map( + const std::string_view& _name, OutputMapType* _parent) const { + add_string_view(_name); + return OutputUnionType{}; +} + +Writer::OutputUnionType Writer::add_union_to_object( + const std::string_view& _name, OutputObjectType* _parent) const { + return OutputUnionType{}; +} + +Writer::OutputUnionType Writer::add_union_to_union( + const size_t _index, OutputUnionType* _parent) const { + (*archive_)(_index); + return OutputUnionType{}; +} + +Writer::OutputVarType Writer::add_null_to_array( + OutputArrayType* _parent) const { + return OutputVarType{}; +} + +Writer::OutputVarType Writer::add_null_to_map( + const std::string_view& _name, OutputMapType* _parent) const { + add_string_view(_name); + return OutputVarType{}; +} + +Writer::OutputVarType Writer::add_null_to_object( + const std::string_view& _name, OutputObjectType* _parent) const { + return OutputVarType{}; +} + +Writer::OutputVarType Writer::add_null_to_union( + const size_t _index, OutputUnionType* _parent) const { + (*archive_)(_index); + return OutputVarType{}; +} + +void Writer::end_array(OutputArrayType* _arr) const noexcept {} + +void Writer::end_map(OutputMapType* _map) const noexcept {} + +void Writer::end_object(OutputObjectType* _obj) const noexcept {} + +void Writer::add_string_view(const std::string_view& _str) const { + (*archive_)(::cereal::make_size_tag(_str.size())); + (*archive_)(::cereal::binary_data(_str.data(), _str.size())); +} + +} // namespace rfl::cereal From 93bd6da48b243fb0a2059cb3f9fa280669e5af93 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 30 Mar 2026 08:02:58 +0200 Subject: [PATCH 13/42] Added cereal to the github actions pipeline --- .github/workflows/linux.yaml | 2 +- .github/workflows/windows.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux.yaml b/.github/workflows/linux.yaml index a367e8f6a..9633ab354 100644 --- a/.github/workflows/linux.yaml +++ b/.github/workflows/linux.yaml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - format: ["JSON", "AVRO", "CAPNPROTO", "CBOR", "FLEXBUFFERS", "MSGPACK", "PARQUET", "TOML", "UBJSON", "XML", "YAML", "benchmarks", "headers"] + format: ["JSON", "AVRO", "CAPNPROTO", "CBOR", "CEREAL", "FLEXBUFFERS", "MSGPACK", "PARQUET", "TOML", "UBJSON", "XML", "YAML", "benchmarks", "headers"] compiler: [llvm, gcc] compiler-version: [11, 12, 13, 14, 16, 17, 18] cxx: [20, 23] diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index cc191e6d7..6aa4e3a03 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - format: ["JSON", "AVRO", "CAPNPROTO", "CBOR", "FLEXBUFFERS", "MSGPACK", "PARQUET", "TOML", "UBJSON", "XML", "YAML", "benchmarks"] + format: ["JSON", "AVRO", "CAPNPROTO", "CBOR", "CEREAL", "FLEXBUFFERS", "MSGPACK", "PARQUET", "TOML", "UBJSON", "XML", "YAML", "benchmarks"] name: "windows-msvc (${{ matrix.format }})" concurrency: group: "windows-${{ github.ref }}-${{ github.job }}-${{ matrix.format }}" From 9c9014d5161b3d8a4700e48cc8579543b8609c16 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 30 Mar 2026 08:10:33 +0200 Subject: [PATCH 14/42] Fixed typo --- include/rfl/cereal/read.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rfl/cereal/read.hpp b/include/rfl/cereal/read.hpp index 57ca4092b..ceca8406e 100644 --- a/include/rfl/cereal/read.hpp +++ b/include/rfl/cereal/read.hpp @@ -48,7 +48,7 @@ auto read(const concepts::ContiguousByteContainer auto& _bytes) { template auto read(std::istream& _stream) { try { - ::cereal::BinaryInputArchive archive(_stream); + ::cereal::PortableBinaryInputArchive archive(_stream); return read(archive); } catch (std::exception& e) { return Result>( From 58afb32e624bb97a9165a2c315730555a767a084 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 30 Mar 2026 08:10:38 +0200 Subject: [PATCH 15/42] Fixed documentation --- MR_DESCRIPTION.md | 385 ------------------------------- docs/supported_formats/cereal.md | 41 +--- 2 files changed, 3 insertions(+), 423 deletions(-) delete mode 100644 MR_DESCRIPTION.md diff --git a/MR_DESCRIPTION.md b/MR_DESCRIPTION.md deleted file mode 100644 index 08c3ee6d2..000000000 --- a/MR_DESCRIPTION.md +++ /dev/null @@ -1,385 +0,0 @@ -# Fix 30 bugs found during code review - -## Summary - -This MR fixes 30 bugs across core types, JSON, XML, TOML, MsgPack, BSON, Avro, Cap'n Proto, and CLI modules. Each bug has a regression test that fails before the fix and passes after. - -**Stats:** 39 files changed, 1197 insertions, 52 deletions. - ---- - -## Discussion: Breaking API Changes - -The following 3 fixes change public API behavior. They are correct from a safety standpoint, but may break downstream code. Please review whether a deprecation period or alternative approach is preferred. - -### 1. `Validator::get()`/`value()` return mutable reference - -> **Breaking change:** Removes non-const overloads of `get()`, `value()`, `operator*()`, `operator()()`. -> Code like `validator.get() = new_value` will no longer compile. -> -> **Conflicts with API convention:** PR #613 established that wrapper types should provide `operator*`, `.value()`, `.get()`, and `operator()`. This fix removes the non-const variants to prevent mutation that bypasses validation. An alternative would be to keep non-const overloads but route them through re-validation (i.e. a validating setter). - -**File:** `include/rfl/Validator.hpp:98-99, 114-115` - -**Problem:** Non-const `get()`, `value()`, `operator*()`, `operator()()` return `T&`, allowing direct write that bypasses validation. - -**Test:** `tests/generic/test_validator.cpp` — `validator_get_does_not_allow_invalid_mutation`. Uses `std::is_assignable_v` to verify `get()` returns `const T&`. - -**Fix:** Removed all non-const overloads (`get()`, `value()`, `operator*()`, `operator()()`). - ---- - -### 2. `Literal(const std::string&)` is implicit - -> **Breaking change:** Adds `explicit` to the string constructor. -> Code like `rfl::Literal<"a","b"> lit = my_string;` will no longer compile; must use `rfl::Literal<"a","b">(my_string)` instead. - -**File:** `include/rfl/Literal.hpp:46` - -**Problem:** Non-explicit constructor allows implicit `std::string` → `Literal` conversion, which throws on invalid input. Surprising for users. - -**Test:** `tests/generic/test_literal.cpp` — `literal_constructor_is_explicit`. Uses `std::is_convertible_v` to verify implicit conversion is disabled. - -**Fix:** Added `explicit` keyword. - ---- - -### 3. `TaggedUnion` — typo `discrimininator_` - -> **Breaking change:** Renames the public static member `discrimininator_` → `discriminator_`. -> Code referencing `TaggedUnion<...>::discrimininator_` will no longer compile. -> A `[[deprecated]]` alias could be added to ease migration if desired. - -**File:** `include/rfl/TaggedUnion.hpp:15` - -**Problem:** Extra 'n' — `discrimininator_` instead of `discriminator_`. - -**Test:** `tests/generic/test_tagged_union.cpp` — `tagged_union_discriminator_spelling`. Uses concepts to check member name existence. - -**Fix:** Renamed to `discriminator_`. Also fixed the typo in the error message string literal `"discrimininator"` → `"discriminator"` in `Parser_tagged_union.hpp:151`. - ---- - -## Bugs Fixed - -### 4. `Tuple::operator<=>` always returns `equivalent` - -**File:** `include/rfl/Tuple.hpp:107` - -**Problem:** The condition `_ordering != equivalent` is checked on first iteration where `_ordering` is initialized to `equivalent` — the body never executes. The operator returns `equivalent` for any two tuples. - -**Test:** `tests/generic/test_tuple.cpp` — `tuple_spaceship_not_always_equivalent`, `tuple_spaceship_less`. Verifies `Tuple(1) <=> Tuple(2)` returns `less`, not `equivalent`. - -**Fix:** Changed `!=` to `==` so the comparison body executes when ordering is still undetermined. - ---- - -### 5. `Result::operator=(const Result&)` does not update `success_` - -**File:** `include/rfl/Result.hpp:210-216` - -**Problem:** The converting assignment copies `t_or_err_` but never updates `success_`. After assigning an error `Result` into a value `Result`, `success_` remains `true` — `.value()` reads garbage. - -**Test:** `tests/generic/test_result.cpp` — `result_cross_type_assign_error_clears_success`. Assigns error `Result` to value `Result`, checks the target is falsy. - -**Fix:** Replaced raw union copy with proper `destroy()` + `success_` update + `move_from_other()`. - ---- - -### 6. `Validator::operator=(U&&)` is `noexcept` but throws - -**File:** `include/rfl/Validator.hpp:77` - -**Problem:** The operator calls `.value()` which throws `std::runtime_error` on validation failure. Marked `noexcept` — any validation error calls `std::terminate`. - -**Test:** `tests/generic/test_validator.cpp` — `validator_assign_rvalue_operator_is_noexcept_but_can_throw`. Uses `std::is_nothrow_assignable_v` to verify the operator is no longer `noexcept`. - -**Fix:** Removed `noexcept` specifier. - ---- - -### 7. `Generic::to_int()` silently truncates `int64_t` to `int` - -**File:** `include/rfl/Generic.hpp:175-188` - -**Problem:** `static_cast(_v)` with no range check. Values outside `[INT_MIN, INT_MAX]` are silently truncated. - -**Test:** `tests/generic/test_generic.cpp` — `generic_to_int_rejects_overflow`, `generic_to_int_rejects_underflow`. Passes `INT_MAX+1` and `INT_MIN-1`, expects failure. - -**Fix:** Added range check before the cast, returning an error for out-of-range values. - ---- - -### 8. `Box`/`Ref` `operator<=>` compares pointer addresses - -**Files:** `include/rfl/Box.hpp:130`, `include/rfl/Ref.hpp:118` - -**Problem:** `_b1.ptr() <=> _b2.ptr()` compares memory addresses instead of pointed-to values. Two `Box(42)` with different allocations compare as unequal. - -**Test:** `tests/generic/test_box.cpp` — `box_spaceship_compares_values_not_pointers`. `tests/generic/test_ref.cpp` — `ref_spaceship_compares_values_not_pointers`. - -**Fix:** Changed to `*_b1 <=> *_b2` (dereference before compare). - ---- - -### 9. `Field`/`Flatten`/`Skip` cross-type move constructor copies instead of moving - -**Files:** `include/rfl/Field.hpp:37`, `include/rfl/Flatten.hpp:32`, `include/rfl/internal/Skip.hpp:43` - -**Problem:** `value_(_field.get())` — `get()` returns lvalue reference, so the copy constructor is called instead of move. - -**Test:** `tests/generic/test_field.cpp` — `field_cross_type_move_does_not_copy`. `tests/generic/test_flatten.cpp` — `flatten_cross_type_move_does_not_copy`. `tests/generic/test_skip.cpp` — `skip_cross_type_move_does_not_copy`. Use `MoveTracker` types that count copies vs moves. - -**Fix:** Changed to `value_(std::move(_field.get()))` in all three types. - ---- - -### 10. `Result::value_or(T&&)` returns dangling rvalue reference - -**File:** `include/rfl/Result.hpp:295` - -**Problem:** Returns `T&&` instead of `T`. If the default argument is a temporary, the caller gets a dangling reference. - -**Test:** `tests/generic/test_result.cpp` — `result_value_or_returns_value_not_rvalue_ref`. Checks return type is `T`, not `T&&`. - -**Fix:** Changed return type from `T&&` to `T`. - ---- - -### 11. `Result::error() &&` returns `Error&` instead of `Error&&` - -**File:** `include/rfl/Result.hpp:335` - -**Problem:** The rvalue-qualified overload returns `Error&` but `get_err() &&` returns `Error&&`. Prevents move semantics on rvalue Results. - -**Test:** `tests/generic/test_result.cpp` — `result_error_rvalue_qualified_returns_rvalue_ref`. Checks return type is `Error&&`. - -**Fix:** Changed return type from `Error&` to `Error&&`. - ---- - -### 12. `Object::difference_type` is unsigned - -**File:** `include/rfl/Object.hpp:29` - -**Problem:** `using difference_type = typename DataType::size_type` — `size_type` is unsigned, but the C++ standard requires `difference_type` to be signed. - -**Test:** `tests/generic/test_object.cpp` — `object_difference_type_is_signed`. Uses `std::is_signed_v`. - -**Fix:** Changed to `typename DataType::difference_type`. - ---- - -### 13. `transform_camel_case` prepends `_` to names starting with uppercase - -**File:** `include/rfl/internal/transform_case.hpp:60-73` - -**Problem:** When `_i == 0` and the first character is uppercase, the function unconditionally prepends `'_'`. `"MyField"` becomes `"_my_field"` instead of `"my_field"`. - -**Test:** `tests/json/test_regression_bugs.cpp` — `camel_case_to_snake_case_no_leading_underscore`. Serializes a struct with `CamelCaseToSnakeCase` and checks output. - -**Fix:** Added `sizeof...(chars) > 0` guard so `'_'` is only inserted between characters, not at the beginning. - ---- - -### 14. `Timestamp::to_time_t()` double-subtracts timezone offset - -**File:** `include/rfl/Timestamp.hpp:120` - -**Problem:** `timegm(&tm) - tm_.tm_gmtoff` — `timegm()` already returns UTC. Subtracting `tm_gmtoff` applies the timezone correction twice. Non-Windows only. - -**Test:** `tests/json/test_regression_bugs.cpp` — `timestamp_to_time_t_no_double_timezone_correction`. Sets `tm_gmtoff = 3600` and verifies `to_time_t()` returns the correct UTC epoch. - -**Fix:** Removed `- tm_.tm_gmtoff`. - ---- - -### 15. `FieldVariantParser` — wrong error message - -**File:** `include/rfl/parsing/FieldVariantParser.hpp:47-50` - -**Problem:** When zero matching fields are found, the error says "found more than one" instead of "found none". - -**Test:** `tests/json/test_regression_bugs.cpp` — `field_variant_empty_object_error_message`. Parses `"{}"` and checks the error message does not contain "more than one". - -**Fix:** Changed message to "found none". - ---- - -### 16. `json::Writer` reads past `string_view` bounds - -**File:** `src/rfl/json/Writer.cpp:68, :91, :113` - -**Problem:** `yyjson_mut_strcpy(_name.data())` reads until `\0`, but `string_view::data()` is not null-terminated. Reads past buffer for substrings. - -**Test:** `tests/json/test_regression_bugs.cpp` — `json_writer_handles_non_null_terminated_field_names`. Creates a `string_view` substring and verifies the JSON key matches the view's length. - -**Fix:** Changed all three call sites from `yyjson_mut_strcpy` to `yyjson_mut_strncpy` with explicit size. - ---- - -### 17. `cli::Reader` — `stoull` accepts negative numbers for unsigned types - -**File:** `include/rfl/cli/Reader.hpp:77` - -**Problem:** `std::stoull("-1")` wraps to `ULLONG_MAX` without error. `static_cast` wraps again to 65535. - -**Test:** `tests/cli/test_regression_bugs.cpp` — `cli_rejects_negative_for_unsigned`. Passes `--port=-1` and expects failure. - -**Fix:** Added explicit check for leading `-` character, returning an error for negative input. - ---- - -### 18. `cli::Reader` — no range check on unsigned narrowing cast - -**File:** `include/rfl/cli/Reader.hpp:77` - -**Problem:** `static_cast(stoull(str))` silently truncates. `stoull("99999")` for `uint16_t` produces 34463. - -**Test:** `tests/cli/test_regression_bugs.cpp` — `cli_rejects_out_of_range_for_narrow_type`. Passes `--port=99999` for `uint16_t` and expects failure. - -**Fix:** Added `std::numeric_limits::max()` bounds check before the cast. - ---- - -### 19. `cli::Reader` — no range check on signed narrowing cast - -**File:** `include/rfl/cli/Reader.hpp:101-102` - -**Problem:** `static_cast(stoll(str))` silently truncates for `int8_t`/`int16_t`/`int32_t`. `stoll("200")` cast to `int8_t` wraps to -56. - -**Test:** `tests/cli/test_regression_bugs.cpp` — `cli_rejects_out_of_range_for_signed_narrow_type`, `cli_rejects_large_negative_for_signed_narrow_type`. Passes `--level=200` and `--level=-200` for `int8_t` and expects failure. - -**Fix:** Added `std::numeric_limits::min()`/`max()` bounds check before the cast. - ---- - -### 20. `cli::Reader` — `std::stod` is locale-dependent - -**File:** `include/rfl/cli/Reader.hpp:64` - -**Problem:** `std::stod` uses the C locale. In locales with comma decimal separator (e.g. `de_DE`), `"3.14"` parses as `3.0`. - -**Test:** `tests/cli/test_regression_bugs.cpp` — `cli_float_parsing_ignores_locale`. Sets `de_DE` locale, parses `--rate=3.14`, checks result is `3.14`. - -**Fix:** Replaced `std::stod` with `std::from_chars` which is locale-independent. - ---- - -### 21. `capnproto::read(istream, schema)` — infinite recursion - -**File:** `include/rfl/capnproto/read.hpp:79-82` - -**Problem:** The overload `read(std::istream&, const Schema&)` calls `read(_stream, _schema)` — resolves to itself. Stack overflow. - -**Test:** `tests/capnproto/compile_fail/capnproto_read_istream_schema.cpp` — instantiates the overload. Before the fix, GCC rejects it as "use of auto before deduction" (infinite recursion detected at compile time). After the fix, the file compiles successfully, verifying the overload resolves correctly. - -**Fix:** Read bytes from stream into a vector, then call `read(bytes.data(), bytes.size(), _schema)`. - ---- - -### 22. `avro::SchemaImpl` move-assignment — double-decrement / use-after-free - -**File:** `src/rfl/avro/SchemaImpl.cpp:34-42` - -**Problem:** Move-assignment neither releases the old `iface_` (leak) nor nullifies `_other.iface_` (double-decrement when `_other`'s destructor runs). - -**Test:** `tests/avro/test_regression_bugs.cpp` — `schema_impl_move_assignment_no_double_free`. Move-assigns two `SchemaImpl` objects, verifies source `iface_` is null after move. - -**Fix:** Added `avro_value_iface_decref`/`avro_schema_decref` for the old iface before reassignment, and `_other.iface_ = nullptr` after. - ---- - -### 23. `xml::Reader` uses `stoi` for all integer types - -**File:** `include/rfl/xml/Reader.hpp:99-105` - -**Problem:** `std::stoi` returns `int` (32-bit). For `int64_t`/`uint64_t` fields, values beyond `INT_MAX` throw `out_of_range` or are silently truncated. Additionally, `static_cast` after `stoll`/`stoull` silently truncates for narrow types like `int16_t`/`uint16_t`. - -**Test:** `tests/xml/test_regression_bugs.cpp` — `read_int64_above_int32_max`, `read_int64_large_negative`, `read_uint64_large_value`, `read_rejects_out_of_range_for_int16`, `read_rejects_negative_for_uint16`, `read_rejects_large_negative_for_int16`. - -**Fix:** Replaced with `std::from_chars` which parses directly into the target type, handling overflow, negative-for-unsigned, and narrowing without manual checks. - ---- - -### 24. `toml::Writer` — `at_path()` dereferences as path, crashes on dotted keys - -**File:** `src/rfl/toml/Writer.cpp:26, :40` - -**Problem:** After `emplace("a.b", ...)`, `at_path("a.b")` interprets the dot as a path separator and looks for `table["a"]["b"]` which doesn't exist — returns invalid node — `as_table()`/`as_array()` returns nullptr. - -**Test:** `tests/toml/test_regression_bugs.cpp` — `field_name_with_dot_does_not_crash`, `array_field_name_with_dot_does_not_crash`. Use `rfl::Rename<"a.b", ...>` to create dotted field names. - -**Fix:** Replaced `at_path(_name)` with `operator[](_name)` which does literal key lookup. - ---- - -### 25. `msgpack::read` ignores `msgpack_unpack` return value - -**File:** `include/rfl/msgpack/read.hpp:35-36` - -**Problem:** If `msgpack_unpack` fails, the return value is not checked. `deserialized` remains uninitialized — garbage is parsed, producing misleading downstream errors like "Could not cast to a map". - -**Test:** `tests/msgpack/test_regression_bugs.cpp` — `read_single_invalid_byte_returns_meaningful_error`, `read_truncated_data_returns_meaningful_error`. Pass invalid/truncated data and verify the error doesn't mention "cast". - -**Fix:** Check return value; return `"Failed to unpack msgpack data."` error on failure. Accept both `MSGPACK_UNPACK_SUCCESS` and `MSGPACK_UNPACK_EXTRA_BYTES`. - ---- - -### 26. `msgpack::Writer` stores `uint64_t` as signed `int64` - -**File:** `include/rfl/msgpack/Writer.hpp:133-134` - -**Problem:** All integers are packed via `msgpack_pack_int64(static_cast(...))`. For `uint64_t` values above `INT64_MAX`, the cast wraps to negative — msgpack stores them as `NEGATIVE_INTEGER`. - -**Test:** `tests/msgpack/test_regression_bugs.cpp` — `uint64_above_int64_max_stored_as_positive`, `uint64_max_stored_as_positive`. Verify raw msgpack wire type is `POSITIVE_INTEGER`. - -**Fix:** Added a separate `std::is_unsigned` branch that calls `msgpack_pack_uint64`. - ---- - -### 27. `bson::Reader` rejects integer BSON values for float fields - -**File:** `include/rfl/bson/Reader.hpp:127-132` - -**Problem:** The float branch checks `btype != BSON_TYPE_DOUBLE` and rejects everything else — even though the error message claims `int32`/`int64`/`date_time` are valid. - -**Test:** `tests/bson/test_regression_bugs.cpp` — `read_int64_into_double_field`, `read_large_int64_into_double_field`. Write an int struct, read it back as a double struct. - -**Fix:** Changed to a `switch` accepting `BSON_TYPE_DOUBLE`, `INT32`, `INT64`, and `DATE_TIME`. - ---- - -### 28. `delete`/`delete[]` mismatch in test - -**File:** `tests/json/test_pointer_fields.cpp` - -**Problem:** A raw pointer field allocated with `new[]` was freed with `delete` instead of `delete[]`. - -**Test:** Pre-existing test; fix prevents undefined behavior under sanitizers. - -**Fix:** Changed `delete` to `delete[]`. - ---- - -### 29. `Flatten(U&&)` universal reference constructor copies instead of forwarding - -**File:** `include/rfl/Flatten.hpp:40` - -**Problem:** `value_(_value)` — `_value` is a named parameter (lvalue), so the copy constructor is always called even when an rvalue is passed. Should be `value_(std::forward(_value))`. - -**Test:** `tests/generic/test_flatten.cpp` — `flatten_universal_ref_ctor_forwards_rvalue`. Constructs `Flatten` from `FlatDerived&&` and verifies rvalue conversion is used. - -**Fix:** Changed to `value_(std::forward(_value))`. - ---- - -### 30. `Flatten::operator=(Flatten&&)` forwards `Flatten` instead of `U` - -**File:** `include/rfl/Flatten.hpp:103-105` - -**Problem:** `std::forward(_f)` forwards the `Flatten` object, not `U`. Assignment `value_ = Flatten` looks for implicit conversion which uses `get()` (lvalue ref) — copy instead of move. Causes hard compile error for types without `Flatten` → `Type` conversion. - -**Test:** `tests/generic/compile_fail/flatten_cross_type_move_assign.cpp` — compile test that instantiates the cross-type move assignment. Verifies compilation succeeds after the fix. - -**Fix:** Changed to `value_ = std::move(_f.get())`. diff --git a/docs/supported_formats/cereal.md b/docs/supported_formats/cereal.md index eff62bb01..12ac42e65 100644 --- a/docs/supported_formats/cereal.md +++ b/docs/supported_formats/cereal.md @@ -3,8 +3,7 @@ For Cereal support, you must also include the header `` and link to the [Cereal](https://uscilab.github.io/cereal/) library. Furthermore, when compiling reflect-cpp, you need to pass `-DREFLECTCPP_CEREAL=ON` to cmake. -Cereal is a C++ serialization library that provides multiple archive formats including binary, portable binary, JSON, and XML formats. - +Cereal is a C++ serialization library that provides multiple archive formats including binary, portable binary, JSON, and XML formats, even though we use the portable binary format by default. ## Reading and writing Suppose you have a struct like this: @@ -61,40 +60,6 @@ rfl::cereal::write(person, std::cout) << std::endl; (Since Cereal binary format is a binary format, the readability of this will be limited, but it might be useful for debugging). -## Using different Cereal archive formats - -By default, reflect-cpp uses Cereal's `BinaryOutputArchive` and `BinaryInputArchive`. However, you can use other archive formats by using the template functions directly: - -```cpp -#include -#include -#include -#include - -const auto person = Person{...}; - -// Using JSON archive -std::stringstream json_stream; -{ - cereal::JSONOutputArchive archive(json_stream); - rfl::cereal::write(person, archive); -} - -// Using XML archive -std::stringstream xml_stream; -{ - cereal::XMLOutputArchive archive(xml_stream); - rfl::cereal::write(person, archive); -} - -// Using Portable Binary archive (endian-safe) -std::stringstream pb_stream; -{ - cereal::PortableBinaryOutputArchive archive(pb_stream); - rfl::cereal::write(person, archive); -} -``` - ## Custom constructors One of the great things about C++ is that it gives you control over @@ -104,7 +69,7 @@ For large and complex systems of structs, it is often a good idea to split up your code into smaller compilation units. You can do so using custom constructors. For the Cereal format, these must be a static function on your struct or class called -`from_cereal_obj` that take a `rfl::cereal::Reader::InputVarType` as input and return +`from_cereal_obj` that take a `rfl::cereal::Reader::InputVarType` as input and return the class or the class wrapped in `rfl::Result`. In your header file you can write something like this: @@ -115,7 +80,7 @@ struct Person { rfl::Rename<"lastName", std::string> last_name; rfl::Timestamp<"%Y-%m-%d"> birthday; - using InputVarType = typename rfl::cereal::Reader::InputVarType; + using InputVarType = rfl::cereal::Reader::InputVarType; static rfl::Result from_cereal_obj(const InputVarType& _obj); }; ``` From c1e63d3d9eaaf3742aadce5cec010ef1fd75285e Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Tue, 31 Mar 2026 07:22:46 +0200 Subject: [PATCH 16/42] Fixed documentation --- docs/supported_formats/cereal.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/supported_formats/cereal.md b/docs/supported_formats/cereal.md index 12ac42e65..a4012e2c3 100644 --- a/docs/supported_formats/cereal.md +++ b/docs/supported_formats/cereal.md @@ -3,7 +3,8 @@ For Cereal support, you must also include the header `` and link to the [Cereal](https://uscilab.github.io/cereal/) library. Furthermore, when compiling reflect-cpp, you need to pass `-DREFLECTCPP_CEREAL=ON` to cmake. -Cereal is a C++ serialization library that provides multiple archive formats including binary, portable binary, JSON, and XML formats, even though we use the portable binary format by default. +Cereal is a C++ serialization library that provides multiple archive formats including binary, portable binary, JSON, and XML formats, even though we use the portable binary format by default. + ## Reading and writing Suppose you have a struct like this: @@ -69,7 +70,7 @@ For large and complex systems of structs, it is often a good idea to split up your code into smaller compilation units. You can do so using custom constructors. For the Cereal format, these must be a static function on your struct or class called -`from_cereal_obj` that take a `rfl::cereal::Reader::InputVarType` as input and return +`from_cereal_obj` that takes a `rfl::cereal::Reader::InputVarType` as input and return the class or the class wrapped in `rfl::Result`. In your header file you can write something like this: From abdef1b3157ec88ca343f9a8988a80ce15149669 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Tue, 31 Mar 2026 07:23:03 +0200 Subject: [PATCH 17/42] Minor fixes --- src/rfl/cereal/Writer.cpp | 35 +++++++++++++++++------------------ tests/cereal/test_box.cpp | 3 +-- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/rfl/cereal/Writer.cpp b/src/rfl/cereal/Writer.cpp index 8e64bb1a0..7518c650b 100644 --- a/src/rfl/cereal/Writer.cpp +++ b/src/rfl/cereal/Writer.cpp @@ -48,9 +48,7 @@ Writer::OutputUnionType Writer::union_as_root() const { return OutputUnionType{}; } -Writer::OutputVarType Writer::null_as_root() const { - return OutputVarType{}; -} +Writer::OutputVarType Writer::null_as_root() const { return OutputVarType{}; } Writer::OutputArrayType Writer::add_array_to_array( const size_t _size, OutputArrayType* _parent) const { @@ -58,9 +56,9 @@ Writer::OutputArrayType Writer::add_array_to_array( return OutputArrayType{}; } -Writer::OutputArrayType Writer::add_array_to_map( - const std::string_view& _name, const size_t _size, - OutputMapType* _parent) const { +Writer::OutputArrayType Writer::add_array_to_map(const std::string_view& _name, + const size_t _size, + OutputMapType* _parent) const { add_string_view(_name); (*archive_)(::cereal::make_size_tag(_size)); return OutputArrayType{}; @@ -80,16 +78,16 @@ Writer::OutputArrayType Writer::add_array_to_union( return OutputArrayType{}; } -Writer::OutputMapType Writer::add_map_to_array( - const size_t _size, OutputArrayType* _parent) const { +Writer::OutputMapType Writer::add_map_to_array(const size_t _size, + OutputArrayType* _parent) const { (*archive_)(::cereal::make_size_tag(_size)); return OutputMapType{}; } -Writer::OutputMapType Writer::add_map_to_map( - const std::string_view& _name, const size_t _size, - OutputMapType* _parent) const { - (*archive_)(std::string(_name)); +Writer::OutputMapType Writer::add_map_to_map(const std::string_view& _name, + const size_t _size, + OutputMapType* _parent) const { + add_string_view(_name); (*archive_)(::cereal::make_size_tag(_size)); return OutputMapType{}; } @@ -101,8 +99,9 @@ Writer::OutputMapType Writer::add_map_to_object( return OutputMapType{}; } -Writer::OutputMapType Writer::add_map_to_union( - const size_t _index, const size_t _size, OutputUnionType* _parent) const { +Writer::OutputMapType Writer::add_map_to_union(const size_t _index, + const size_t _size, + OutputUnionType* _parent) const { (*archive_)(_index); (*archive_)(::cereal::make_size_tag(_size)); return OutputMapType{}; @@ -137,8 +136,8 @@ Writer::OutputUnionType Writer::add_union_to_array( return OutputUnionType{}; } -Writer::OutputUnionType Writer::add_union_to_map( - const std::string_view& _name, OutputMapType* _parent) const { +Writer::OutputUnionType Writer::add_union_to_map(const std::string_view& _name, + OutputMapType* _parent) const { add_string_view(_name); return OutputUnionType{}; } @@ -159,8 +158,8 @@ Writer::OutputVarType Writer::add_null_to_array( return OutputVarType{}; } -Writer::OutputVarType Writer::add_null_to_map( - const std::string_view& _name, OutputMapType* _parent) const { +Writer::OutputVarType Writer::add_null_to_map(const std::string_view& _name, + OutputMapType* _parent) const { add_string_view(_name); return OutputVarType{}; } diff --git a/tests/cereal/test_box.cpp b/tests/cereal/test_box.cpp index 18a79cfbd..06c44cb22 100644 --- a/tests/cereal/test_box.cpp +++ b/tests/cereal/test_box.cpp @@ -38,7 +38,6 @@ TEST(cereal, test_box) { const DecisionTree tree = DecisionTree{.leaf_or_node = std::move(node)}; - // TODO - // write_and_read(tree); + write_and_read(tree); } } // namespace test_box From 9745c9ddaf763c6c10d35fa6e4de5406e030d1b9 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Tue, 31 Mar 2026 07:43:56 +0200 Subject: [PATCH 18/42] Do not convert name to std::string --- include/rfl/parsing/ViewReader.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rfl/parsing/ViewReader.hpp b/include/rfl/parsing/ViewReader.hpp index e7d491125..9c8d18127 100644 --- a/include/rfl/parsing/ViewReader.hpp +++ b/include/rfl/parsing/ViewReader.hpp @@ -75,7 +75,7 @@ class ViewReader { auto res = Parser::read(_r, _var); if (!res) { std::stringstream stream; - stream << "Failed to parse field '" << std::string(name) + stream << "Failed to parse field '" << name << "': " << res.error().what(); _errors->emplace_back(Error(stream.str())); return; From b296c83cf8f965f1d2e14beefcda2218c3dba4c8 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Tue, 31 Mar 2026 07:53:18 +0200 Subject: [PATCH 19/42] Get rid of the tmp folder --- tests/cereal/test_save_load.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/cereal/test_save_load.cpp b/tests/cereal/test_save_load.cpp index 16c878cd6..0d6c494f6 100644 --- a/tests/cereal/test_save_load.cpp +++ b/tests/cereal/test_save_load.cpp @@ -15,13 +15,12 @@ struct Person { }; TEST(cereal, test_save_load) { - const auto homer = - Person{.first_name = "Homer", - .last_name = "Simpson", - .email = "homer@simpson.com", - .age = 45}; + const auto homer = Person{.first_name = "Homer", + .last_name = "Simpson", + .email = "homer@simpson.com", + .age = 45}; - rfl::cereal::save("/tmp/homer.cereal", homer); + rfl::cereal::save("homer.cereal", homer); const auto homer2 = rfl::cereal::load("/tmp/homer.cereal").value(); From 9d55bea985700c540ae1d96556ad33a7155cc460 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Tue, 31 Mar 2026 07:54:55 +0200 Subject: [PATCH 20/42] Fixed typo --- tests/cereal/test_save_load.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cereal/test_save_load.cpp b/tests/cereal/test_save_load.cpp index 0d6c494f6..c719d434b 100644 --- a/tests/cereal/test_save_load.cpp +++ b/tests/cereal/test_save_load.cpp @@ -22,7 +22,7 @@ TEST(cereal, test_save_load) { rfl::cereal::save("homer.cereal", homer); - const auto homer2 = rfl::cereal::load("/tmp/homer.cereal").value(); + const auto homer2 = rfl::cereal::load("homer.cereal").value(); write_and_read(homer2); } From 3cf04d01d6f3491a2865d781da3fda5560213725 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Tue, 31 Mar 2026 16:06:19 +0200 Subject: [PATCH 21/42] emplace_back -> push_back --- include/rfl/parsing/ViewReader.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/rfl/parsing/ViewReader.hpp b/include/rfl/parsing/ViewReader.hpp index 9c8d18127..57c8f925e 100644 --- a/include/rfl/parsing/ViewReader.hpp +++ b/include/rfl/parsing/ViewReader.hpp @@ -77,7 +77,7 @@ class ViewReader { std::stringstream stream; stream << "Failed to parse field '" << name << "': " << res.error().what(); - _errors->emplace_back(Error(stream.str())); + _errors->push_back(Error(stream.str())); return; } if constexpr (std::is_pointer_v) { @@ -109,7 +109,7 @@ class ViewReader { std::stringstream stream; stream << "Failed to parse field '" << _current_name << "': " << res.error().what(); - _errors->emplace_back(Error(stream.str())); + _errors->push_back(Error(stream.str())); return; } extra_fields->emplace(std::string(_current_name), std::move(*res)); @@ -144,7 +144,7 @@ class ViewReader { if (!already_assigned) { std::stringstream stream; stream << "Value named '" << _current_name_or_index << "' not used."; - _errors->emplace_back(Error(stream.str())); + _errors->push_back(Error(stream.str())); } } } From dacad20a3e186932ef8725b64340bee70ca426e4 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Tue, 31 Mar 2026 16:09:20 +0200 Subject: [PATCH 22/42] Revert "emplace_back -> push_back" This reverts commit 3cf04d01d6f3491a2865d781da3fda5560213725. --- include/rfl/parsing/ViewReader.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/rfl/parsing/ViewReader.hpp b/include/rfl/parsing/ViewReader.hpp index 57c8f925e..9c8d18127 100644 --- a/include/rfl/parsing/ViewReader.hpp +++ b/include/rfl/parsing/ViewReader.hpp @@ -77,7 +77,7 @@ class ViewReader { std::stringstream stream; stream << "Failed to parse field '" << name << "': " << res.error().what(); - _errors->push_back(Error(stream.str())); + _errors->emplace_back(Error(stream.str())); return; } if constexpr (std::is_pointer_v) { @@ -109,7 +109,7 @@ class ViewReader { std::stringstream stream; stream << "Failed to parse field '" << _current_name << "': " << res.error().what(); - _errors->push_back(Error(stream.str())); + _errors->emplace_back(Error(stream.str())); return; } extra_fields->emplace(std::string(_current_name), std::move(*res)); @@ -144,7 +144,7 @@ class ViewReader { if (!already_assigned) { std::stringstream stream; stream << "Value named '" << _current_name_or_index << "' not used."; - _errors->push_back(Error(stream.str())); + _errors->emplace_back(Error(stream.str())); } } } From d06d5f6fb5b0923ee96bfe65794f153d1c06463f Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Tue, 31 Mar 2026 16:11:16 +0200 Subject: [PATCH 23/42] Move the errs --- include/rfl/parsing/ViewReader.hpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/include/rfl/parsing/ViewReader.hpp b/include/rfl/parsing/ViewReader.hpp index 9c8d18127..1269f26ae 100644 --- a/include/rfl/parsing/ViewReader.hpp +++ b/include/rfl/parsing/ViewReader.hpp @@ -77,7 +77,8 @@ class ViewReader { std::stringstream stream; stream << "Failed to parse field '" << name << "': " << res.error().what(); - _errors->emplace_back(Error(stream.str())); + auto err = Error{stream.str()}; + _errors->emplace_back(std::move(err)); return; } if constexpr (std::is_pointer_v) { @@ -109,7 +110,8 @@ class ViewReader { std::stringstream stream; stream << "Failed to parse field '" << _current_name << "': " << res.error().what(); - _errors->emplace_back(Error(stream.str())); + auto err = Error{stream.str()}; + _errors->emplace_back(std::move(err)); return; } extra_fields->emplace(std::string(_current_name), std::move(*res)); @@ -144,7 +146,8 @@ class ViewReader { if (!already_assigned) { std::stringstream stream; stream << "Value named '" << _current_name_or_index << "' not used."; - _errors->emplace_back(Error(stream.str())); + auto err = Error{stream.str()}; + _errors->emplace_back(std::move(err)); } } } From efaaa3336cc36130e150a4c49577e24199c0255a Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Tue, 31 Mar 2026 17:33:23 +0200 Subject: [PATCH 24/42] Revert "Move the errs" This reverts commit d06d5f6fb5b0923ee96bfe65794f153d1c06463f. --- include/rfl/parsing/ViewReader.hpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/include/rfl/parsing/ViewReader.hpp b/include/rfl/parsing/ViewReader.hpp index 1269f26ae..9c8d18127 100644 --- a/include/rfl/parsing/ViewReader.hpp +++ b/include/rfl/parsing/ViewReader.hpp @@ -77,8 +77,7 @@ class ViewReader { std::stringstream stream; stream << "Failed to parse field '" << name << "': " << res.error().what(); - auto err = Error{stream.str()}; - _errors->emplace_back(std::move(err)); + _errors->emplace_back(Error(stream.str())); return; } if constexpr (std::is_pointer_v) { @@ -110,8 +109,7 @@ class ViewReader { std::stringstream stream; stream << "Failed to parse field '" << _current_name << "': " << res.error().what(); - auto err = Error{stream.str()}; - _errors->emplace_back(std::move(err)); + _errors->emplace_back(Error(stream.str())); return; } extra_fields->emplace(std::string(_current_name), std::move(*res)); @@ -146,8 +144,7 @@ class ViewReader { if (!already_assigned) { std::stringstream stream; stream << "Value named '" << _current_name_or_index << "' not used."; - auto err = Error{stream.str()}; - _errors->emplace_back(std::move(err)); + _errors->emplace_back(Error(stream.str())); } } } From df70f1c0b407ba521871d4b730befaee0798f287 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Tue, 31 Mar 2026 17:46:15 +0200 Subject: [PATCH 25/42] Use ptr_cast instead of std::launder --- include/rfl/Result.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/include/rfl/Result.hpp b/include/rfl/Result.hpp index d03517205..37b635f4e 100644 --- a/include/rfl/Result.hpp +++ b/include/rfl/Result.hpp @@ -12,6 +12,8 @@ #include #include +#include "internal/ptr_cast.hpp" + namespace rfl { /// Defines the error class to be returned when something went wrong @@ -398,27 +400,25 @@ class Result { } T&& get_t() && noexcept { - return std::move(*std::launder(reinterpret_cast(t_or_err_.data()))); + return std::move(*internal::ptr_cast(t_or_err_.data())); } - T& get_t() & noexcept { - return *std::launder(reinterpret_cast(t_or_err_.data())); - } + T& get_t() & noexcept { return *internal::ptr_cast(t_or_err_.data()); } const T& get_t() const& noexcept { - return *std::launder(reinterpret_cast(t_or_err_.data())); + return *internal::ptr_cast(t_or_err_.data()); } Error&& get_err() && noexcept { - return std::move(*std::launder(reinterpret_cast(t_or_err_.data()))); + return std::move(*internal::ptr_cast(t_or_err_.data())); } Error& get_err() & noexcept { - return *std::launder(reinterpret_cast(t_or_err_.data())); + return *internal::ptr_cast(t_or_err_.data()); } const Error& get_err() const& noexcept { - return *std::launder(reinterpret_cast(t_or_err_.data())); + return *internal::ptr_cast(t_or_err_.data()); } void move_from_other(Result& _other) noexcept { From 45f1336bce30b3fd129bff6beb3e10e47a00d3db Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 1 Apr 2026 05:34:18 +0200 Subject: [PATCH 26/42] Revert "Use ptr_cast instead of std::launder" This reverts commit df70f1c0b407ba521871d4b730befaee0798f287. --- include/rfl/Result.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/include/rfl/Result.hpp b/include/rfl/Result.hpp index 37b635f4e..d03517205 100644 --- a/include/rfl/Result.hpp +++ b/include/rfl/Result.hpp @@ -12,8 +12,6 @@ #include #include -#include "internal/ptr_cast.hpp" - namespace rfl { /// Defines the error class to be returned when something went wrong @@ -400,25 +398,27 @@ class Result { } T&& get_t() && noexcept { - return std::move(*internal::ptr_cast(t_or_err_.data())); + return std::move(*std::launder(reinterpret_cast(t_or_err_.data()))); } - T& get_t() & noexcept { return *internal::ptr_cast(t_or_err_.data()); } + T& get_t() & noexcept { + return *std::launder(reinterpret_cast(t_or_err_.data())); + } const T& get_t() const& noexcept { - return *internal::ptr_cast(t_or_err_.data()); + return *std::launder(reinterpret_cast(t_or_err_.data())); } Error&& get_err() && noexcept { - return std::move(*internal::ptr_cast(t_or_err_.data())); + return std::move(*std::launder(reinterpret_cast(t_or_err_.data()))); } Error& get_err() & noexcept { - return *internal::ptr_cast(t_or_err_.data()); + return *std::launder(reinterpret_cast(t_or_err_.data())); } const Error& get_err() const& noexcept { - return *internal::ptr_cast(t_or_err_.data()); + return *std::launder(reinterpret_cast(t_or_err_.data())); } void move_from_other(Result& _other) noexcept { From d6a91c92c203ff735ae647109bafcd1b6a98f9f0 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 1 Apr 2026 05:40:23 +0200 Subject: [PATCH 27/42] Declare Result.hpp a system header to suppress warnings --- include/rfl/Result.hpp | 11 +++++++++++ include/rfl/internal/bind_to_tuple.hpp | 1 - 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/include/rfl/Result.hpp b/include/rfl/Result.hpp index d03517205..4e206ad4c 100644 --- a/include/rfl/Result.hpp +++ b/include/rfl/Result.hpp @@ -2,7 +2,18 @@ #define RFL_RESULT_HPP_ #ifdef REFLECTCPP_USE_STD_EXPECTED + #include + +#else + +// Necessary workaround due to a false positive warning in GCC 14. +#if __GNUC__ +#ifndef __clang__ +#pragma GCC system_header +#endif +#endif + #endif #include diff --git a/include/rfl/internal/bind_to_tuple.hpp b/include/rfl/internal/bind_to_tuple.hpp index e2b28b27e..2eab5b357 100644 --- a/include/rfl/internal/bind_to_tuple.hpp +++ b/include/rfl/internal/bind_to_tuple.hpp @@ -7,7 +7,6 @@ #include "../Tuple.hpp" #include "../always_false.hpp" -// #include "is_named_tuple.hpp" //Not here #include "num_fields.hpp" namespace rfl::internal { From baa4d4e683368a4a34e55d9ac96bed89734ae930 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 1 Apr 2026 06:10:21 +0200 Subject: [PATCH 28/42] Next attempt --- include/rfl/Result.hpp | 11 ----------- include/rfl/parsing/ViewReader.hpp | 12 ++++++++++++ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/include/rfl/Result.hpp b/include/rfl/Result.hpp index 4e206ad4c..d03517205 100644 --- a/include/rfl/Result.hpp +++ b/include/rfl/Result.hpp @@ -2,18 +2,7 @@ #define RFL_RESULT_HPP_ #ifdef REFLECTCPP_USE_STD_EXPECTED - #include - -#else - -// Necessary workaround due to a false positive warning in GCC 14. -#if __GNUC__ -#ifndef __clang__ -#pragma GCC system_header -#endif -#endif - #endif #include diff --git a/include/rfl/parsing/ViewReader.hpp b/include/rfl/parsing/ViewReader.hpp index 9c8d18127..bcf2f0a36 100644 --- a/include/rfl/parsing/ViewReader.hpp +++ b/include/rfl/parsing/ViewReader.hpp @@ -14,6 +14,18 @@ #include "Parser_base.hpp" #include "schemaful/IsSchemafulReader.hpp" +#ifndef REFLECTCPP_USE_STD_EXPECTED + +// Necessary workaround due to a false +// positive in GCC. +#if __GNUC__ +#ifndef __clang__ +#pragma GCC system_header +#endif +#endif + +#endif + namespace rfl::parsing { template From 88e6fa5060d61f8d368d654da4a6a48d45c983a8 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 1 Apr 2026 06:17:44 +0200 Subject: [PATCH 29/42] Next attempt --- include/rfl/parsing/NamedTupleParser.hpp | 12 ++++++++++++ include/rfl/parsing/ViewReader.hpp | 12 ------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/rfl/parsing/NamedTupleParser.hpp b/include/rfl/parsing/NamedTupleParser.hpp index 7f423e04e..7d0ee0c99 100644 --- a/include/rfl/parsing/NamedTupleParser.hpp +++ b/include/rfl/parsing/NamedTupleParser.hpp @@ -1,6 +1,18 @@ #ifndef RFL_PARSING_NAMEDTUPLEPARSER_HPP_ #define RFL_PARSING_NAMEDTUPLEPARSER_HPP_ +#ifndef REFLECTCPP_USE_STD_EXPECTED + +// Necessary workaround due to a false +// positive in GCC. +#if __GNUC__ +#ifndef __clang__ +#pragma GCC system_header +#endif +#endif + +#endif + #include #include #include diff --git a/include/rfl/parsing/ViewReader.hpp b/include/rfl/parsing/ViewReader.hpp index bcf2f0a36..9c8d18127 100644 --- a/include/rfl/parsing/ViewReader.hpp +++ b/include/rfl/parsing/ViewReader.hpp @@ -14,18 +14,6 @@ #include "Parser_base.hpp" #include "schemaful/IsSchemafulReader.hpp" -#ifndef REFLECTCPP_USE_STD_EXPECTED - -// Necessary workaround due to a false -// positive in GCC. -#if __GNUC__ -#ifndef __clang__ -#pragma GCC system_header -#endif -#endif - -#endif - namespace rfl::parsing { template From 01f490739c2c4aa7ab52791c0b611e63186bdc8a Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 1 Apr 2026 06:28:52 +0200 Subject: [PATCH 30/42] Disable warning --- include/rfl.hpp | 13 +++++++++++++ include/rfl/parsing/NamedTupleParser.hpp | 12 ------------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/include/rfl.hpp b/include/rfl.hpp index a8c6dcffc..0113383b1 100644 --- a/include/rfl.hpp +++ b/include/rfl.hpp @@ -7,6 +7,13 @@ #pragma warning(disable : 4101) #endif +#if __GNUC__ +#ifndef __clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-overflow" +#endif +#endif + #include "rfl/AddStructName.hpp" #include "rfl/AddTagsToVariants.hpp" #include "rfl/AllOf.hpp" @@ -92,4 +99,10 @@ #pragma warning(pop) #endif +#if __GNUC__ +#ifndef __clang__ +#pragma GCC diagnostic pop +#endif +#endif + #endif diff --git a/include/rfl/parsing/NamedTupleParser.hpp b/include/rfl/parsing/NamedTupleParser.hpp index 7d0ee0c99..7f423e04e 100644 --- a/include/rfl/parsing/NamedTupleParser.hpp +++ b/include/rfl/parsing/NamedTupleParser.hpp @@ -1,18 +1,6 @@ #ifndef RFL_PARSING_NAMEDTUPLEPARSER_HPP_ #define RFL_PARSING_NAMEDTUPLEPARSER_HPP_ -#ifndef REFLECTCPP_USE_STD_EXPECTED - -// Necessary workaround due to a false -// positive in GCC. -#if __GNUC__ -#ifndef __clang__ -#pragma GCC system_header -#endif -#endif - -#endif - #include #include #include From 32bd925ea2f5e92ae1ef3e5c8a28d241ea3b64c5 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 1 Apr 2026 06:44:23 +0200 Subject: [PATCH 31/42] Next attempt --- include/rfl.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rfl.hpp b/include/rfl.hpp index 0113383b1..e9166dc03 100644 --- a/include/rfl.hpp +++ b/include/rfl.hpp @@ -10,7 +10,7 @@ #if __GNUC__ #ifndef __clang__ #pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wstringop-overflow" +#pragma GCC diagnostic ignored "-Wstringop-overflow=" #endif #endif From 86239cf7e3cfa29d033208881be1ca02d85b3658 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 1 Apr 2026 06:49:46 +0200 Subject: [PATCH 32/42] Next attempt --- include/rfl.hpp | 13 ------------- include/rfl/Result.hpp | 4 ++-- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/include/rfl.hpp b/include/rfl.hpp index e9166dc03..a8c6dcffc 100644 --- a/include/rfl.hpp +++ b/include/rfl.hpp @@ -7,13 +7,6 @@ #pragma warning(disable : 4101) #endif -#if __GNUC__ -#ifndef __clang__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wstringop-overflow=" -#endif -#endif - #include "rfl/AddStructName.hpp" #include "rfl/AddTagsToVariants.hpp" #include "rfl/AllOf.hpp" @@ -99,10 +92,4 @@ #pragma warning(pop) #endif -#if __GNUC__ -#ifndef __clang__ -#pragma GCC diagnostic pop -#endif -#endif - #endif diff --git a/include/rfl/Result.hpp b/include/rfl/Result.hpp index d03517205..c982effa7 100644 --- a/include/rfl/Result.hpp +++ b/include/rfl/Result.hpp @@ -20,11 +20,11 @@ class Error { Error() = default; Error(const std::string& _what) : what_(_what) {} - Error(std::string&& _what) : what_(std::move(_what)) {} + Error(std::string _what) : what_(std::move(_what)) {} ~Error() = default; - Error(const Error& e) = default; + Error(const Error& e) noexcept = default; Error(Error&& e) noexcept = default; Error& operator=(const Error& _other) = default; From 06349408ddcad79c13f0d240fd5dadf44e39d5ab Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 1 Apr 2026 06:52:03 +0200 Subject: [PATCH 33/42] Revert "Next attempt" This reverts commit 86239cf7e3cfa29d033208881be1ca02d85b3658. --- include/rfl.hpp | 13 +++++++++++++ include/rfl/Result.hpp | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/include/rfl.hpp b/include/rfl.hpp index a8c6dcffc..e9166dc03 100644 --- a/include/rfl.hpp +++ b/include/rfl.hpp @@ -7,6 +7,13 @@ #pragma warning(disable : 4101) #endif +#if __GNUC__ +#ifndef __clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-overflow=" +#endif +#endif + #include "rfl/AddStructName.hpp" #include "rfl/AddTagsToVariants.hpp" #include "rfl/AllOf.hpp" @@ -92,4 +99,10 @@ #pragma warning(pop) #endif +#if __GNUC__ +#ifndef __clang__ +#pragma GCC diagnostic pop +#endif +#endif + #endif diff --git a/include/rfl/Result.hpp b/include/rfl/Result.hpp index c982effa7..d03517205 100644 --- a/include/rfl/Result.hpp +++ b/include/rfl/Result.hpp @@ -20,11 +20,11 @@ class Error { Error() = default; Error(const std::string& _what) : what_(_what) {} - Error(std::string _what) : what_(std::move(_what)) {} + Error(std::string&& _what) : what_(std::move(_what)) {} ~Error() = default; - Error(const Error& e) noexcept = default; + Error(const Error& e) = default; Error(Error&& e) noexcept = default; Error& operator=(const Error& _other) = default; From 7d77c8f5e3924f49f617fb17a496e9a6f24f67a3 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 1 Apr 2026 07:06:05 +0200 Subject: [PATCH 34/42] Collect errors as raw string --- include/rfl/parsing/MapParser.hpp | 2 +- include/rfl/parsing/MapReader.hpp | 8 ++++---- include/rfl/parsing/NamedTupleParser.hpp | 16 ++++++++-------- include/rfl/parsing/Parser_rfl_variant.hpp | 8 ++++---- include/rfl/parsing/Parser_variant.hpp | 8 ++++---- include/rfl/parsing/ViewReader.hpp | 10 +++++----- include/rfl/parsing/ViewReaderWithDefault.hpp | 10 +++++----- ...iewReaderWithDefaultAndStrippedFieldNames.hpp | 6 +++--- .../parsing/ViewReaderWithStrippedFieldNames.hpp | 6 +++--- include/rfl/parsing/to_single_error_message.hpp | 7 +++---- 10 files changed, 40 insertions(+), 41 deletions(-) diff --git a/include/rfl/parsing/MapParser.hpp b/include/rfl/parsing/MapParser.hpp index 03432c9a6..33f21b464 100644 --- a/include/rfl/parsing/MapParser.hpp +++ b/include/rfl/parsing/MapParser.hpp @@ -61,7 +61,7 @@ struct MapParser { private: static Result make_map(const R& _r, const auto& _obj_or_map) { MapType map; - std::vector errors; + std::vector errors; const auto map_reader = MapReader(&_r, &map, &errors); if constexpr (schemaful::IsSchemafulReader) { diff --git a/include/rfl/parsing/MapReader.hpp b/include/rfl/parsing/MapReader.hpp index 6e932a613..3301066a4 100644 --- a/include/rfl/parsing/MapReader.hpp +++ b/include/rfl/parsing/MapReader.hpp @@ -23,7 +23,7 @@ class MapReader { std::remove_cvref_t; public: - MapReader(const R* _r, MapType* _map, std::vector* _errors) + MapReader(const R* _r, MapType* _map, std::vector* _errors) : r_(_r), map_(_map), errors_(_errors) {} ~MapReader() = default; @@ -34,8 +34,8 @@ class MapReader { if (res) { map_->emplace(std::move(*res)); } else { - errors_->push_back(Error("Failed to parse field '" + std::string(_name) + - "': " + res.error().what())); + errors_->push_back("Failed to parse field '" + std::string(_name) + + "': " + res.error().what()); } } @@ -109,7 +109,7 @@ class MapReader { MapType* map_; /// Collects any errors we may have come across. - std::vector* errors_; + std::vector* errors_; }; } // namespace rfl::parsing diff --git a/include/rfl/parsing/NamedTupleParser.hpp b/include/rfl/parsing/NamedTupleParser.hpp index 7f423e04e..c100d26c7 100644 --- a/include/rfl/parsing/NamedTupleParser.hpp +++ b/include/rfl/parsing/NamedTupleParser.hpp @@ -245,10 +245,10 @@ struct NamedTupleParser { /// Generates error messages for when fields are missing. template - static void handle_one_missing_field(const std::array& _found, - const NamedTupleType& _view, - std::array* _set, - std::vector* _errors) noexcept { + static void handle_one_missing_field( + const std::array& _found, const NamedTupleType& _view, + std::array* _set, + std::vector* _errors) noexcept { using FieldType = internal::nth_element_t<_i, FieldTypes...>; using ValueType = std::remove_reference_t< std::remove_pointer_t>; @@ -265,7 +265,7 @@ struct NamedTupleParser { std::stringstream stream; stream << "Field named '" << std::string(current_name) << "' not found."; - _errors->emplace_back(Error(stream.str())); + _errors->emplace_back(stream.str()); } else if constexpr (!internal::has_default_val_v) { if constexpr (!std::is_const_v) { @@ -283,7 +283,7 @@ struct NamedTupleParser { template static void handle_missing_fields( const std::array& _found, const NamedTupleType& _view, - std::array* _set, std::vector* _errors, + std::array* _set, std::vector* _errors, std::integer_sequence) noexcept { (handle_one_missing_field<_is>(_found, _view, _set, _errors), ...); } @@ -305,7 +305,7 @@ struct NamedTupleParser { found.fill(false); auto set = std::array(); set.fill(false); - std::vector errors; + std::vector errors; const auto reader = ViewReaderType(&_r, _view, &found, &set, &errors); if constexpr (_no_field_names) { const auto err = _r.read_array(reader, _obj_or_arr); @@ -329,7 +329,7 @@ struct NamedTupleParser { static std::optional read_object_or_array_with_default( const R& _r, const InputObjectOrArrayType& _obj_or_arr, NamedTupleType* _view) noexcept { - std::vector errors; + std::vector errors; const auto reader = ViewReaderWithDefaultType(&_r, _view, &errors); if constexpr (_no_field_names) { const auto err = _r.read_array(reader, _obj_or_arr); diff --git a/include/rfl/parsing/Parser_rfl_variant.hpp b/include/rfl/parsing/Parser_rfl_variant.hpp index 8c5dd2f5b..63f598d55 100644 --- a/include/rfl/parsing/Parser_rfl_variant.hpp +++ b/include/rfl/parsing/Parser_rfl_variant.hpp @@ -71,7 +71,7 @@ class Parser, ProcessorsType> { } else { std::optional> result; - std::vector errors; + std::vector errors; errors.reserve(sizeof...(AlternativeTypes)); read_variant( _r, _var, &result, &errors, @@ -189,7 +189,7 @@ class Parser, ProcessorsType> { static void read_one_alternative( const R& _r, const InputVarType& _var, std::optional>* _result, - std::vector* _errors) noexcept { + std::vector* _errors) noexcept { if (!*_result) { using AltType = std::remove_cvref_t>; @@ -197,7 +197,7 @@ class Parser, ProcessorsType> { if (res) { *_result = std::move(*res); } else { - _errors->emplace_back(std::move(res.error())); + _errors->emplace_back(std::move(res.error().what())); } } } @@ -206,7 +206,7 @@ class Parser, ProcessorsType> { static void read_variant( const R& _r, const InputVarType& _var, std::optional>* _result, - std::vector* _errors, + std::vector* _errors, std::integer_sequence) noexcept { (read_one_alternative<_is>(_r, _var, _result, _errors), ...); } diff --git a/include/rfl/parsing/Parser_variant.hpp b/include/rfl/parsing/Parser_variant.hpp index 55b1da69f..0820858b1 100644 --- a/include/rfl/parsing/Parser_variant.hpp +++ b/include/rfl/parsing/Parser_variant.hpp @@ -87,7 +87,7 @@ class Parser, ProcessorsType> { } else { std::optional> result; - std::vector errors; + std::vector errors; errors.reserve(sizeof...(AlternativeTypes)); read_variant( _r, _var, &result, &errors, @@ -216,7 +216,7 @@ class Parser, ProcessorsType> { static void read_one_alternative( const R& _r, const InputVarType& _var, std::optional>* _result, - std::vector* _errors) noexcept { + std::vector* _errors) noexcept { if (!*_result) { using AltType = std::remove_cvref_t>; @@ -224,7 +224,7 @@ class Parser, ProcessorsType> { if (res) { _result->emplace(std::move(*res)); } else { - _errors->emplace_back(res.error()); + _errors->emplace_back(res.error().what()); } } } @@ -233,7 +233,7 @@ class Parser, ProcessorsType> { static void read_variant( const R& _r, const InputVarType& _var, std::optional>* _result, - std::vector* _errors, + std::vector* _errors, std::integer_sequence) noexcept { (read_one_alternative<_is>(_r, _var, _result, _errors), ...); } diff --git a/include/rfl/parsing/ViewReader.hpp b/include/rfl/parsing/ViewReader.hpp index 9c8d18127..60b772590 100644 --- a/include/rfl/parsing/ViewReader.hpp +++ b/include/rfl/parsing/ViewReader.hpp @@ -24,7 +24,7 @@ class ViewReader { public: ViewReader(const R* _r, ViewType* _view, std::array* _found, - std::array* _set, std::vector* _errors) + std::array* _set, std::vector* _errors) : r_(_r), view_(_view), found_(_found), set_(_set), errors_(_errors) {} ~ViewReader() = default; @@ -77,7 +77,7 @@ class ViewReader { std::stringstream stream; stream << "Failed to parse field '" << name << "': " << res.error().what(); - _errors->emplace_back(Error(stream.str())); + _errors->emplace_back(stream.str()); return; } if constexpr (std::is_pointer_v) { @@ -109,7 +109,7 @@ class ViewReader { std::stringstream stream; stream << "Failed to parse field '" << _current_name << "': " << res.error().what(); - _errors->emplace_back(Error(stream.str())); + _errors->emplace_back(stream.str()); return; } extra_fields->emplace(std::string(_current_name), std::move(*res)); @@ -144,7 +144,7 @@ class ViewReader { if (!already_assigned) { std::stringstream stream; stream << "Value named '" << _current_name_or_index << "' not used."; - _errors->emplace_back(Error(stream.str())); + _errors->emplace_back(stream.str()); } } } @@ -184,7 +184,7 @@ class ViewReader { std::array* set_; /// Collects any errors we may have come across. - std::vector* errors_; + std::vector* errors_; }; } // namespace rfl::parsing diff --git a/include/rfl/parsing/ViewReaderWithDefault.hpp b/include/rfl/parsing/ViewReaderWithDefault.hpp index 331fb2807..fa7d10021 100644 --- a/include/rfl/parsing/ViewReaderWithDefault.hpp +++ b/include/rfl/parsing/ViewReaderWithDefault.hpp @@ -24,7 +24,7 @@ class ViewReaderWithDefault { public: ViewReaderWithDefault(const R* _r, ViewType* _view, - std::vector* _errors) + std::vector* _errors) : r_(_r), view_(_view), errors_(_errors) { found_->fill(false); } @@ -63,7 +63,7 @@ class ViewReaderWithDefault { std::stringstream stream; stream << "Failed to parse field '" << std::string(name) << "': " << res.error().what(); - _errors->emplace_back(Error(stream.str())); + _errors->emplace_back(stream.str()); return; } if constexpr (std::is_pointer_v) { @@ -89,7 +89,7 @@ class ViewReaderWithDefault { std::stringstream stream; stream << "Failed to parse field '" << _current_name << "': " << res.error().what(); - _errors->emplace_back(Error(stream.str())); + _errors->emplace_back(stream.str()); return; } extra_fields->emplace(std::string(_current_name), std::move(*res)); @@ -118,7 +118,7 @@ class ViewReaderWithDefault { stream << "Value named '" << std::string(_current_name) << "' not used. Remove the rfl::NoExtraFields processor or add " "rfl::ExtraFields to avoid this error message."; - _errors->emplace_back(Error(stream.str())); + _errors->emplace_back(stream.str()); } } } @@ -154,7 +154,7 @@ class ViewReaderWithDefault { rfl::Ref> found_; /// Collects any errors we may have come across. - std::vector* errors_; + std::vector* errors_; }; } // namespace rfl::parsing diff --git a/include/rfl/parsing/ViewReaderWithDefaultAndStrippedFieldNames.hpp b/include/rfl/parsing/ViewReaderWithDefaultAndStrippedFieldNames.hpp index e9f067982..329f57d43 100644 --- a/include/rfl/parsing/ViewReaderWithDefaultAndStrippedFieldNames.hpp +++ b/include/rfl/parsing/ViewReaderWithDefaultAndStrippedFieldNames.hpp @@ -22,7 +22,7 @@ class ViewReaderWithDefaultAndStrippedFieldNames { public: ViewReaderWithDefaultAndStrippedFieldNames(const R* _r, ViewType* _view, - std::vector* _errors) + std::vector* _errors) : i_(0), r_(_r), view_(_view), errors_(_errors) {} ~ViewReaderWithDefaultAndStrippedFieldNames() = default; @@ -66,7 +66,7 @@ class ViewReaderWithDefaultAndStrippedFieldNames { std::stringstream stream; stream << "Failed to parse field '" << std::string(name) << "': " << res.error().what(); - _errors->emplace_back(Error(stream.str())); + _errors->emplace_back(stream.str()); return; } if constexpr (std::is_pointer_v) { @@ -116,7 +116,7 @@ class ViewReaderWithDefaultAndStrippedFieldNames { ViewType* view_; /// Collects any errors we may have come across. - std::vector* errors_; + std::vector* errors_; }; } // namespace rfl::parsing diff --git a/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp b/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp index 7cdde5be0..2a1da3379 100644 --- a/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp +++ b/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp @@ -25,7 +25,7 @@ class ViewReaderWithStrippedFieldNames { ViewReaderWithStrippedFieldNames(const R* _r, ViewType* _view, std::array* _found, std::array* _set, - std::vector* _errors) + std::vector* _errors) : i_(0), r_(_r), view_(_view), @@ -69,7 +69,7 @@ class ViewReaderWithStrippedFieldNames { std::stringstream stream; stream << "Failed to parse field '" << std::string(name) << "': " << res.error().what(); - _errors->emplace_back(Error(stream.str())); + _errors->emplace_back(stream.str()); return; } if constexpr (std::is_pointer_v) { @@ -128,7 +128,7 @@ class ViewReaderWithStrippedFieldNames { std::array* set_; /// Collects any errors we may have come across. - std::vector* errors_; + std::vector* errors_; }; } // namespace rfl::parsing diff --git a/include/rfl/parsing/to_single_error_message.hpp b/include/rfl/parsing/to_single_error_message.hpp index 1f0663510..4e7ec6a0f 100644 --- a/include/rfl/parsing/to_single_error_message.hpp +++ b/include/rfl/parsing/to_single_error_message.hpp @@ -13,11 +13,11 @@ namespace rfl::parsing { /// Combines a set of errors to a single, readable error message. inline std::string to_single_error_message( - std::vector _errors, + std::vector _errors, std::optional _msg_prefix = std::nullopt, size_t _err_limit = 10) { if (_errors.size() == 1) { - return _errors[0].what(); + return _errors[0]; } else { std::stringstream stream; stream << (_msg_prefix @@ -26,8 +26,7 @@ inline std::string to_single_error_message( for (size_t i = 0; i < _errors.size() && i < _err_limit; ++i) { stream << "\n" << i + 1 << ") " - << internal::strings::replace_all(_errors.at(i).what(), "\n", - "\n "); + << internal::strings::replace_all(_errors.at(i), "\n", "\n "); } if (_errors.size() > _err_limit) { stream << "\n...\nMore than " << _err_limit From 52de80b247650e012be05fd14bc29fa56d3e58c7 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 1 Apr 2026 07:19:06 +0200 Subject: [PATCH 35/42] name as string_view --- include/rfl/parsing/ViewReader.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rfl/parsing/ViewReader.hpp b/include/rfl/parsing/ViewReader.hpp index 60b772590..5f34e308d 100644 --- a/include/rfl/parsing/ViewReader.hpp +++ b/include/rfl/parsing/ViewReader.hpp @@ -75,7 +75,7 @@ class ViewReader { auto res = Parser::read(_r, _var); if (!res) { std::stringstream stream; - stream << "Failed to parse field '" << name + stream << "Failed to parse field '" << name.string_view() << "': " << res.error().what(); _errors->emplace_back(stream.str()); return; From 8dedd2e6c1e202b631589b4130cac0400b6ee3da Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 1 Apr 2026 07:21:28 +0200 Subject: [PATCH 36/42] Get rid of field name altogeher (temp fix) --- include/rfl/parsing/ViewReader.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/rfl/parsing/ViewReader.hpp b/include/rfl/parsing/ViewReader.hpp index 5f34e308d..f5b1730f8 100644 --- a/include/rfl/parsing/ViewReader.hpp +++ b/include/rfl/parsing/ViewReader.hpp @@ -75,8 +75,7 @@ class ViewReader { auto res = Parser::read(_r, _var); if (!res) { std::stringstream stream; - stream << "Failed to parse field '" << name.string_view() - << "': " << res.error().what(); + stream << "Failed to parse field: " << res.error().what(); _errors->emplace_back(stream.str()); return; } From dd5eaba7bedaad43dec1da6806ca120e52c859ca Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 1 Apr 2026 07:25:56 +0200 Subject: [PATCH 37/42] name not constexpr --- include/rfl/parsing/ViewReader.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/rfl/parsing/ViewReader.hpp b/include/rfl/parsing/ViewReader.hpp index f5b1730f8..124962180 100644 --- a/include/rfl/parsing/ViewReader.hpp +++ b/include/rfl/parsing/ViewReader.hpp @@ -67,15 +67,16 @@ class ViewReader { using OriginalType = typename FieldType::Type; using T = std::remove_cvref_t>; - constexpr auto name = FieldType::name(); if (!(*_already_assigned) && !std::get(*_found) && is_matching(_current_name_or_index)) { std::get(*_found) = true; *_already_assigned = true; auto res = Parser::read(_r, _var); if (!res) { + const auto name = FieldType::name(); std::stringstream stream; - stream << "Failed to parse field: " << res.error().what(); + stream << "Failed to parse field '" << name + << "': " << res.error().what(); _errors->emplace_back(stream.str()); return; } From f887fa3272c8a435b34a85f60a56ccf0559a416c Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 1 Apr 2026 07:51:44 +0200 Subject: [PATCH 38/42] name constexpr --- include/rfl/parsing/ViewReader.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rfl/parsing/ViewReader.hpp b/include/rfl/parsing/ViewReader.hpp index 124962180..990fa8a22 100644 --- a/include/rfl/parsing/ViewReader.hpp +++ b/include/rfl/parsing/ViewReader.hpp @@ -73,7 +73,7 @@ class ViewReader { *_already_assigned = true; auto res = Parser::read(_r, _var); if (!res) { - const auto name = FieldType::name(); + constexpr auto name = FieldType::name(); std::stringstream stream; stream << "Failed to parse field '" << name << "': " << res.error().what(); From acec4c5be48afbc0fd759c67dd6d26aee962dd00 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Thu, 2 Apr 2026 04:04:37 +0200 Subject: [PATCH 39/42] Set local flag --- .gitignore | 1 + CMakeLists.txt | 4 ---- docs/supported_formats/cereal.md | 4 +++- include/rfl.hpp | 13 ------------- tests/CMakeLists.txt | 4 ++-- tests/cereal/CMakeLists.txt | 3 +++ 6 files changed, 9 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 43aa36d0f..b4e879274 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,7 @@ *.bson *.capnproto *.cbor +*.cereal *.csv *.json *.fb diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c8384205..6b8b2408a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -192,10 +192,6 @@ else() target_compile_options(reflectcpp PRIVATE $<$: -Wall -Wextra -Wpedantic -Wshadow -Wconversion - #-Wnull-dereference -Wold-style-cast - #-g3 -fno-omit-frame-pointer - #-fsanitize=address,undefined - #-fno-sanitize-recover=undefined > ) endif() diff --git a/docs/supported_formats/cereal.md b/docs/supported_formats/cereal.md index a4012e2c3..a192b2cfd 100644 --- a/docs/supported_formats/cereal.md +++ b/docs/supported_formats/cereal.md @@ -3,7 +3,9 @@ For Cereal support, you must also include the header `` and link to the [Cereal](https://uscilab.github.io/cereal/) library. Furthermore, when compiling reflect-cpp, you need to pass `-DREFLECTCPP_CEREAL=ON` to cmake. -Cereal is a C++ serialization library that provides multiple archive formats including binary, portable binary, JSON, and XML formats, even though we use the portable binary format by default. +Cereal is a C++ serialization library that provides multiple archive formats including binary, portable binary, JSON, and XML formats, even though we use the portable binary format. + +Note that on some versions of GCC, it is necessary to compile with `-Wno-stringop-overflow`, because of false positive warnings due to a known compiler bug, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110498. ## Reading and writing diff --git a/include/rfl.hpp b/include/rfl.hpp index e9166dc03..a8c6dcffc 100644 --- a/include/rfl.hpp +++ b/include/rfl.hpp @@ -7,13 +7,6 @@ #pragma warning(disable : 4101) #endif -#if __GNUC__ -#ifndef __clang__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wstringop-overflow=" -#endif -#endif - #include "rfl/AddStructName.hpp" #include "rfl/AddTagsToVariants.hpp" #include "rfl/AllOf.hpp" @@ -99,10 +92,4 @@ #pragma warning(pop) #endif -#if __GNUC__ -#ifndef __clang__ -#pragma GCC diagnostic pop -#endif -#endif - #endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bc5aaee1d..6401a7392 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,15 +1,15 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -O2") +# Note: Adding -Wno-stringop-overflow is necessary, because of false positive warnings, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110498 if (MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std:c++20") else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -Wall -Werror -ggdb -ftemplate-backtrace-limit=0") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -Wall -Werror -ggdb -ftemplate-backtrace-limit=0") endif() if (REFLECTCPP_JSON) add_subdirectory(generic) add_subdirectory(json) - # add_subdirectory(alloc) # Disabled: tests detect known leaks in Parser_string_view/Parser_span, not yet fixed add_subdirectory(json_c_arrays_and_inheritance) add_subdirectory(cli) endif () diff --git a/tests/cereal/CMakeLists.txt b/tests/cereal/CMakeLists.txt index f6a8a2928..3c9e28a49 100644 --- a/tests/cereal/CMakeLists.txt +++ b/tests/cereal/CMakeLists.txt @@ -8,6 +8,9 @@ add_executable( ) target_precompile_headers(reflect-cpp-cereal-tests PRIVATE [["rfl.hpp"]] ) +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-stringop-overflow") +endif() target_link_libraries(reflect-cpp-cereal-tests PRIVATE reflectcpp_tests_crt) From 72d059aa91c613cc4305ef888b18666d14d48f3c Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Thu, 2 Apr 2026 12:01:11 +0200 Subject: [PATCH 40/42] Add errors.reserve to help the compiler --- docs/supported_formats/cereal.md | 2 -- include/rfl/parsing/NamedTupleParser.hpp | 8 +++--- include/rfl/parsing/ViewReader.hpp | 28 +++++++++++-------- include/rfl/parsing/ViewReaderWithDefault.hpp | 22 ++++++--------- ...ReaderWithDefaultAndStrippedFieldNames.hpp | 15 ++++------ .../ViewReaderWithStrippedFieldNames.hpp | 15 ++++------ tests/cereal/CMakeLists.txt | 4 --- 7 files changed, 40 insertions(+), 54 deletions(-) diff --git a/docs/supported_formats/cereal.md b/docs/supported_formats/cereal.md index a192b2cfd..e45d21fc9 100644 --- a/docs/supported_formats/cereal.md +++ b/docs/supported_formats/cereal.md @@ -5,8 +5,6 @@ Furthermore, when compiling reflect-cpp, you need to pass `-DREFLECTCPP_CEREAL=O Cereal is a C++ serialization library that provides multiple archive formats including binary, portable binary, JSON, and XML formats, even though we use the portable binary format. -Note that on some versions of GCC, it is necessary to compile with `-Wno-stringop-overflow`, because of false positive warnings due to a known compiler bug, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110498. - ## Reading and writing Suppose you have a struct like this: diff --git a/include/rfl/parsing/NamedTupleParser.hpp b/include/rfl/parsing/NamedTupleParser.hpp index c100d26c7..ba95b6ad4 100644 --- a/include/rfl/parsing/NamedTupleParser.hpp +++ b/include/rfl/parsing/NamedTupleParser.hpp @@ -262,10 +262,8 @@ struct NamedTupleParser { if constexpr (is_required_field) { constexpr auto current_name = internal::nth_element_t<_i, FieldTypes...>::name(); - std::stringstream stream; - stream << "Field named '" << std::string(current_name) - << "' not found."; - _errors->emplace_back(stream.str()); + _errors->emplace_back("Field named '" + std::string(current_name) + + "' not found."); } else if constexpr (!internal::has_default_val_v) { if constexpr (!std::is_const_v) { @@ -306,6 +304,7 @@ struct NamedTupleParser { auto set = std::array(); set.fill(false); std::vector errors; + errors.reserve(size_); const auto reader = ViewReaderType(&_r, _view, &found, &set, &errors); if constexpr (_no_field_names) { const auto err = _r.read_array(reader, _obj_or_arr); @@ -330,6 +329,7 @@ struct NamedTupleParser { const R& _r, const InputObjectOrArrayType& _obj_or_arr, NamedTupleType* _view) noexcept { std::vector errors; + errors.reserve(size_); const auto reader = ViewReaderWithDefaultType(&_r, _view, &errors); if constexpr (_no_field_names) { const auto err = _r.read_array(reader, _obj_or_arr); diff --git a/include/rfl/parsing/ViewReader.hpp b/include/rfl/parsing/ViewReader.hpp index 990fa8a22..2d2ba346f 100644 --- a/include/rfl/parsing/ViewReader.hpp +++ b/include/rfl/parsing/ViewReader.hpp @@ -74,10 +74,8 @@ class ViewReader { auto res = Parser::read(_r, _var); if (!res) { constexpr auto name = FieldType::name(); - std::stringstream stream; - stream << "Failed to parse field '" << name - << "': " << res.error().what(); - _errors->emplace_back(stream.str()); + _errors->emplace_back("Failed to parse field '" + std::string(name) + + "': " + res.error().what()); return; } if constexpr (std::is_pointer_v) { @@ -106,10 +104,9 @@ class ViewReader { } auto res = Parser::read(_r, _var); if (!res) { - std::stringstream stream; - stream << "Failed to parse field '" << _current_name - << "': " << res.error().what(); - _errors->emplace_back(stream.str()); + _errors->emplace_back("Failed to parse field '" + + std::string(_current_name) + + "': " + res.error().what()); return; } extra_fields->emplace(std::string(_current_name), std::move(*res)); @@ -142,13 +139,22 @@ class ViewReader { "Passing rfl::NoExtraFields to a schemaful format does not make " "sense, because schemaful formats cannot have extra fields."); if (!already_assigned) { - std::stringstream stream; - stream << "Value named '" << _current_name_or_index << "' not used."; - _errors->emplace_back(stream.str()); + _errors->emplace_back("Value named '" + + to_string(_current_name_or_index) + + "' not used."); } } } + template + static std::string to_string(const T& _v) { + if constexpr (std::is_integral_v>) { + return std::to_string(_v); + } else { + return std::string(_v); + } + } + template static void move_to(Target* _t, Source* _s) { if constexpr (std::is_const_v) { diff --git a/include/rfl/parsing/ViewReaderWithDefault.hpp b/include/rfl/parsing/ViewReaderWithDefault.hpp index fa7d10021..ec38de803 100644 --- a/include/rfl/parsing/ViewReaderWithDefault.hpp +++ b/include/rfl/parsing/ViewReaderWithDefault.hpp @@ -60,10 +60,8 @@ class ViewReaderWithDefault { *_already_assigned = true; auto res = Parser::read(_r, _var); if (!res) { - std::stringstream stream; - stream << "Failed to parse field '" << std::string(name) - << "': " << res.error().what(); - _errors->emplace_back(stream.str()); + _errors->emplace_back("Failed to parse field '" + std::string(name) + + "': " + res.error().what()); return; } if constexpr (std::is_pointer_v) { @@ -86,10 +84,9 @@ class ViewReaderWithDefault { std::remove_pointer_t>; auto res = Parser::read(_r, _var); if (!res) { - std::stringstream stream; - stream << "Failed to parse field '" << _current_name - << "': " << res.error().what(); - _errors->emplace_back(stream.str()); + _errors->emplace_back("Failed to parse field '" + + std::string(_current_name) + + "': " + res.error().what()); return; } extra_fields->emplace(std::string(_current_name), std::move(*res)); @@ -114,11 +111,10 @@ class ViewReaderWithDefault { } } else if constexpr (ProcessorsType::no_extra_fields_) { if (!already_assigned) { - std::stringstream stream; - stream << "Value named '" << std::string(_current_name) - << "' not used. Remove the rfl::NoExtraFields processor or add " - "rfl::ExtraFields to avoid this error message."; - _errors->emplace_back(stream.str()); + _errors->emplace_back( + "Value named '" + std::string(_current_name) + + "' not used. Remove the rfl::NoExtraFields processor or add " + "rfl::ExtraFields to avoid this error message."); } } } diff --git a/include/rfl/parsing/ViewReaderWithDefaultAndStrippedFieldNames.hpp b/include/rfl/parsing/ViewReaderWithDefaultAndStrippedFieldNames.hpp index 329f57d43..8cffd859d 100644 --- a/include/rfl/parsing/ViewReaderWithDefaultAndStrippedFieldNames.hpp +++ b/include/rfl/parsing/ViewReaderWithDefaultAndStrippedFieldNames.hpp @@ -38,10 +38,8 @@ class ViewReaderWithDefaultAndStrippedFieldNames { /// used when the field names are stripped. std::optional read(const InputVarType& _var) const { if (i_ == size_) { - std::stringstream stream; - stream << "Expected a maximum of " << std::to_string(size_) - << " fields, but got at least one more."; - return Error(stream.str()); + return Error("Expected a maximum of " + std::to_string(size_) + + " fields, but got at least one more."); } assign_to_field_i(*r_, _var, view_, errors_, i_, std::make_integer_sequence()); @@ -63,13 +61,10 @@ class ViewReaderWithDefaultAndStrippedFieldNames { if (_i == i) { auto res = Parser::read(_r, _var); if (!res) { - std::stringstream stream; - stream << "Failed to parse field '" << std::string(name) - << "': " << res.error().what(); - _errors->emplace_back(stream.str()); + _errors->emplace_back("Failed to parse field '" + std::string(name) + + "': " + res.error().what()); return; - } - if constexpr (std::is_pointer_v) { + } if constexpr (std::is_pointer_v) { move_to(rfl::get(*_view), &(*res)); } else { rfl::get(*_view) = std::move(*res); diff --git a/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp b/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp index 2a1da3379..36e81ff68 100644 --- a/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp +++ b/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp @@ -39,10 +39,8 @@ class ViewReaderWithStrippedFieldNames { /// used when the field names are stripped. std::optional read(const InputVarType& _var) const { if (i_ == size_) { - std::stringstream stream; - stream << "Expected a maximum of " << std::to_string(size_) - << " fields, but got at least one more."; - return Error(stream.str()); + return Error("Expected a maximum of " + std::to_string(size_) + + " fields, but got at least one more."); } assign_to_field_i(*r_, _var, view_, errors_, found_, set_, i_, std::make_integer_sequence()); @@ -66,13 +64,10 @@ class ViewReaderWithStrippedFieldNames { std::get(*_found) = true; auto res = Parser::read(_r, _var); if (!res) { - std::stringstream stream; - stream << "Failed to parse field '" << std::string(name) - << "': " << res.error().what(); - _errors->emplace_back(stream.str()); + _errors->emplace_back("Failed to parse field '" + std::string(name) + + "': " + res.error().what()); return; - } - if constexpr (std::is_pointer_v) { + } if constexpr (std::is_pointer_v) { move_to(rfl::get(*_view), &(*res)); } else { rfl::get(*_view) = std::move(*res); diff --git a/tests/cereal/CMakeLists.txt b/tests/cereal/CMakeLists.txt index 3c9e28a49..e3b915fb2 100644 --- a/tests/cereal/CMakeLists.txt +++ b/tests/cereal/CMakeLists.txt @@ -8,10 +8,6 @@ add_executable( ) target_precompile_headers(reflect-cpp-cereal-tests PRIVATE [["rfl.hpp"]] ) -if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-stringop-overflow") -endif() - target_link_libraries(reflect-cpp-cereal-tests PRIVATE reflectcpp_tests_crt) add_custom_command(TARGET reflect-cpp-cereal-tests POST_BUILD From 6624f6291ffa89bef0a0595a9f1de9e384cbc231 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Thu, 2 Apr 2026 12:12:21 +0200 Subject: [PATCH 41/42] Revert "Add errors.reserve to help the compiler" This reverts commit 72d059aa91c613cc4305ef888b18666d14d48f3c. --- docs/supported_formats/cereal.md | 2 ++ include/rfl/parsing/NamedTupleParser.hpp | 8 +++--- include/rfl/parsing/ViewReader.hpp | 28 ++++++++----------- include/rfl/parsing/ViewReaderWithDefault.hpp | 22 +++++++++------ ...ReaderWithDefaultAndStrippedFieldNames.hpp | 15 ++++++---- .../ViewReaderWithStrippedFieldNames.hpp | 15 ++++++---- tests/cereal/CMakeLists.txt | 4 +++ 7 files changed, 54 insertions(+), 40 deletions(-) diff --git a/docs/supported_formats/cereal.md b/docs/supported_formats/cereal.md index e45d21fc9..a192b2cfd 100644 --- a/docs/supported_formats/cereal.md +++ b/docs/supported_formats/cereal.md @@ -5,6 +5,8 @@ Furthermore, when compiling reflect-cpp, you need to pass `-DREFLECTCPP_CEREAL=O Cereal is a C++ serialization library that provides multiple archive formats including binary, portable binary, JSON, and XML formats, even though we use the portable binary format. +Note that on some versions of GCC, it is necessary to compile with `-Wno-stringop-overflow`, because of false positive warnings due to a known compiler bug, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110498. + ## Reading and writing Suppose you have a struct like this: diff --git a/include/rfl/parsing/NamedTupleParser.hpp b/include/rfl/parsing/NamedTupleParser.hpp index ba95b6ad4..c100d26c7 100644 --- a/include/rfl/parsing/NamedTupleParser.hpp +++ b/include/rfl/parsing/NamedTupleParser.hpp @@ -262,8 +262,10 @@ struct NamedTupleParser { if constexpr (is_required_field) { constexpr auto current_name = internal::nth_element_t<_i, FieldTypes...>::name(); - _errors->emplace_back("Field named '" + std::string(current_name) + - "' not found."); + std::stringstream stream; + stream << "Field named '" << std::string(current_name) + << "' not found."; + _errors->emplace_back(stream.str()); } else if constexpr (!internal::has_default_val_v) { if constexpr (!std::is_const_v) { @@ -304,7 +306,6 @@ struct NamedTupleParser { auto set = std::array(); set.fill(false); std::vector errors; - errors.reserve(size_); const auto reader = ViewReaderType(&_r, _view, &found, &set, &errors); if constexpr (_no_field_names) { const auto err = _r.read_array(reader, _obj_or_arr); @@ -329,7 +330,6 @@ struct NamedTupleParser { const R& _r, const InputObjectOrArrayType& _obj_or_arr, NamedTupleType* _view) noexcept { std::vector errors; - errors.reserve(size_); const auto reader = ViewReaderWithDefaultType(&_r, _view, &errors); if constexpr (_no_field_names) { const auto err = _r.read_array(reader, _obj_or_arr); diff --git a/include/rfl/parsing/ViewReader.hpp b/include/rfl/parsing/ViewReader.hpp index 2d2ba346f..990fa8a22 100644 --- a/include/rfl/parsing/ViewReader.hpp +++ b/include/rfl/parsing/ViewReader.hpp @@ -74,8 +74,10 @@ class ViewReader { auto res = Parser::read(_r, _var); if (!res) { constexpr auto name = FieldType::name(); - _errors->emplace_back("Failed to parse field '" + std::string(name) + - "': " + res.error().what()); + std::stringstream stream; + stream << "Failed to parse field '" << name + << "': " << res.error().what(); + _errors->emplace_back(stream.str()); return; } if constexpr (std::is_pointer_v) { @@ -104,9 +106,10 @@ class ViewReader { } auto res = Parser::read(_r, _var); if (!res) { - _errors->emplace_back("Failed to parse field '" + - std::string(_current_name) + - "': " + res.error().what()); + std::stringstream stream; + stream << "Failed to parse field '" << _current_name + << "': " << res.error().what(); + _errors->emplace_back(stream.str()); return; } extra_fields->emplace(std::string(_current_name), std::move(*res)); @@ -139,22 +142,13 @@ class ViewReader { "Passing rfl::NoExtraFields to a schemaful format does not make " "sense, because schemaful formats cannot have extra fields."); if (!already_assigned) { - _errors->emplace_back("Value named '" + - to_string(_current_name_or_index) + - "' not used."); + std::stringstream stream; + stream << "Value named '" << _current_name_or_index << "' not used."; + _errors->emplace_back(stream.str()); } } } - template - static std::string to_string(const T& _v) { - if constexpr (std::is_integral_v>) { - return std::to_string(_v); - } else { - return std::string(_v); - } - } - template static void move_to(Target* _t, Source* _s) { if constexpr (std::is_const_v) { diff --git a/include/rfl/parsing/ViewReaderWithDefault.hpp b/include/rfl/parsing/ViewReaderWithDefault.hpp index ec38de803..fa7d10021 100644 --- a/include/rfl/parsing/ViewReaderWithDefault.hpp +++ b/include/rfl/parsing/ViewReaderWithDefault.hpp @@ -60,8 +60,10 @@ class ViewReaderWithDefault { *_already_assigned = true; auto res = Parser::read(_r, _var); if (!res) { - _errors->emplace_back("Failed to parse field '" + std::string(name) + - "': " + res.error().what()); + std::stringstream stream; + stream << "Failed to parse field '" << std::string(name) + << "': " << res.error().what(); + _errors->emplace_back(stream.str()); return; } if constexpr (std::is_pointer_v) { @@ -84,9 +86,10 @@ class ViewReaderWithDefault { std::remove_pointer_t>; auto res = Parser::read(_r, _var); if (!res) { - _errors->emplace_back("Failed to parse field '" + - std::string(_current_name) + - "': " + res.error().what()); + std::stringstream stream; + stream << "Failed to parse field '" << _current_name + << "': " << res.error().what(); + _errors->emplace_back(stream.str()); return; } extra_fields->emplace(std::string(_current_name), std::move(*res)); @@ -111,10 +114,11 @@ class ViewReaderWithDefault { } } else if constexpr (ProcessorsType::no_extra_fields_) { if (!already_assigned) { - _errors->emplace_back( - "Value named '" + std::string(_current_name) + - "' not used. Remove the rfl::NoExtraFields processor or add " - "rfl::ExtraFields to avoid this error message."); + std::stringstream stream; + stream << "Value named '" << std::string(_current_name) + << "' not used. Remove the rfl::NoExtraFields processor or add " + "rfl::ExtraFields to avoid this error message."; + _errors->emplace_back(stream.str()); } } } diff --git a/include/rfl/parsing/ViewReaderWithDefaultAndStrippedFieldNames.hpp b/include/rfl/parsing/ViewReaderWithDefaultAndStrippedFieldNames.hpp index 8cffd859d..329f57d43 100644 --- a/include/rfl/parsing/ViewReaderWithDefaultAndStrippedFieldNames.hpp +++ b/include/rfl/parsing/ViewReaderWithDefaultAndStrippedFieldNames.hpp @@ -38,8 +38,10 @@ class ViewReaderWithDefaultAndStrippedFieldNames { /// used when the field names are stripped. std::optional read(const InputVarType& _var) const { if (i_ == size_) { - return Error("Expected a maximum of " + std::to_string(size_) + - " fields, but got at least one more."); + std::stringstream stream; + stream << "Expected a maximum of " << std::to_string(size_) + << " fields, but got at least one more."; + return Error(stream.str()); } assign_to_field_i(*r_, _var, view_, errors_, i_, std::make_integer_sequence()); @@ -61,10 +63,13 @@ class ViewReaderWithDefaultAndStrippedFieldNames { if (_i == i) { auto res = Parser::read(_r, _var); if (!res) { - _errors->emplace_back("Failed to parse field '" + std::string(name) + - "': " + res.error().what()); + std::stringstream stream; + stream << "Failed to parse field '" << std::string(name) + << "': " << res.error().what(); + _errors->emplace_back(stream.str()); return; - } if constexpr (std::is_pointer_v) { + } + if constexpr (std::is_pointer_v) { move_to(rfl::get(*_view), &(*res)); } else { rfl::get(*_view) = std::move(*res); diff --git a/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp b/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp index 36e81ff68..2a1da3379 100644 --- a/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp +++ b/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp @@ -39,8 +39,10 @@ class ViewReaderWithStrippedFieldNames { /// used when the field names are stripped. std::optional read(const InputVarType& _var) const { if (i_ == size_) { - return Error("Expected a maximum of " + std::to_string(size_) + - " fields, but got at least one more."); + std::stringstream stream; + stream << "Expected a maximum of " << std::to_string(size_) + << " fields, but got at least one more."; + return Error(stream.str()); } assign_to_field_i(*r_, _var, view_, errors_, found_, set_, i_, std::make_integer_sequence()); @@ -64,10 +66,13 @@ class ViewReaderWithStrippedFieldNames { std::get(*_found) = true; auto res = Parser::read(_r, _var); if (!res) { - _errors->emplace_back("Failed to parse field '" + std::string(name) + - "': " + res.error().what()); + std::stringstream stream; + stream << "Failed to parse field '" << std::string(name) + << "': " << res.error().what(); + _errors->emplace_back(stream.str()); return; - } if constexpr (std::is_pointer_v) { + } + if constexpr (std::is_pointer_v) { move_to(rfl::get(*_view), &(*res)); } else { rfl::get(*_view) = std::move(*res); diff --git a/tests/cereal/CMakeLists.txt b/tests/cereal/CMakeLists.txt index e3b915fb2..3c9e28a49 100644 --- a/tests/cereal/CMakeLists.txt +++ b/tests/cereal/CMakeLists.txt @@ -8,6 +8,10 @@ add_executable( ) target_precompile_headers(reflect-cpp-cereal-tests PRIVATE [["rfl.hpp"]] ) +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-stringop-overflow") +endif() + target_link_libraries(reflect-cpp-cereal-tests PRIVATE reflectcpp_tests_crt) add_custom_command(TARGET reflect-cpp-cereal-tests POST_BUILD From b3f626d6cb1f3b84ffb75d1148af37344d2c7eca Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Thu, 2 Apr 2026 12:18:11 +0200 Subject: [PATCH 42/42] Only add the reserves --- docs/supported_formats/cereal.md | 2 -- include/rfl/parsing/NamedTupleParser.hpp | 2 ++ tests/cereal/CMakeLists.txt | 4 ---- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/supported_formats/cereal.md b/docs/supported_formats/cereal.md index a192b2cfd..e45d21fc9 100644 --- a/docs/supported_formats/cereal.md +++ b/docs/supported_formats/cereal.md @@ -5,8 +5,6 @@ Furthermore, when compiling reflect-cpp, you need to pass `-DREFLECTCPP_CEREAL=O Cereal is a C++ serialization library that provides multiple archive formats including binary, portable binary, JSON, and XML formats, even though we use the portable binary format. -Note that on some versions of GCC, it is necessary to compile with `-Wno-stringop-overflow`, because of false positive warnings due to a known compiler bug, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110498. - ## Reading and writing Suppose you have a struct like this: diff --git a/include/rfl/parsing/NamedTupleParser.hpp b/include/rfl/parsing/NamedTupleParser.hpp index c100d26c7..d31d39414 100644 --- a/include/rfl/parsing/NamedTupleParser.hpp +++ b/include/rfl/parsing/NamedTupleParser.hpp @@ -306,6 +306,7 @@ struct NamedTupleParser { auto set = std::array(); set.fill(false); std::vector errors; + errors.reserve(size_); const auto reader = ViewReaderType(&_r, _view, &found, &set, &errors); if constexpr (_no_field_names) { const auto err = _r.read_array(reader, _obj_or_arr); @@ -330,6 +331,7 @@ struct NamedTupleParser { const R& _r, const InputObjectOrArrayType& _obj_or_arr, NamedTupleType* _view) noexcept { std::vector errors; + errors.reserve(size_); const auto reader = ViewReaderWithDefaultType(&_r, _view, &errors); if constexpr (_no_field_names) { const auto err = _r.read_array(reader, _obj_or_arr); diff --git a/tests/cereal/CMakeLists.txt b/tests/cereal/CMakeLists.txt index 3c9e28a49..e3b915fb2 100644 --- a/tests/cereal/CMakeLists.txt +++ b/tests/cereal/CMakeLists.txt @@ -8,10 +8,6 @@ add_executable( ) target_precompile_headers(reflect-cpp-cereal-tests PRIVATE [["rfl.hpp"]] ) -if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-stringop-overflow") -endif() - target_link_libraries(reflect-cpp-cereal-tests PRIVATE reflectcpp_tests_crt) add_custom_command(TARGET reflect-cpp-cereal-tests POST_BUILD