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 a18a82ef..4c838420 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 9ac863e0..1232851a 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 a085b7b6..ed8660d7 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 00000000..eff62bb0 --- /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 00000000..f4c3bc62 --- /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 00000000..9b7cd0a2 --- /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 00000000..3905baa9 --- /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 00000000..f1f8a01f --- /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 00000000..c026375e --- /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 00000000..9d2e1d3e --- /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 00000000..09a9f662 --- /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 00000000..ca9d8aec --- /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 24e7e1fd..cc55300f 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 128a89c7..d401db03 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 00000000..ec1ff849 --- /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 9f3c2a90..bc5aaee1 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 00000000..f6a8a292 --- /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 00000000..81412d43 --- /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 00000000..06c44cb2 --- /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 00000000..92520072 --- /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 00000000..b9a1dfee --- /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 00000000..c1ce79a2 --- /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 00000000..aa33cd53 --- /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 00000000..9375dced --- /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 00000000..16c878cd --- /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 00000000..00eb5baf --- /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 00000000..42cbe3ad --- /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 00000000..55459a2d --- /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 00000000..588643cc --- /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 00000000..b2c7e3d1 --- /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 00000000..fc84a002 --- /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 57790383..64404444 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 3905baa9..7f01cb9b 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 f1f8a01f..4db05da5 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 9d2e1d3e..57ca4092 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 ca9d8aec..f157f521 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 06c44cb2..18a79cfb 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 92520072..17bb88fa 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 7f01cb9b..1b2b7fc4 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 4db05da5..3503a093 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 8bdbfbdd..e7d49112 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 8ee2d79e..331fb280 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 7b12a2d8..e9f06798 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 5787a1e0..7cdde5be 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 f6a8a292..c9524187 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 9375dced..1da49c68 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 1b2b7fc4..e913c1a1 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 3503a093..b4054ef3 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 1da49c68..6cc2baaa 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 fc84a002..1f41ad59 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 e913c1a1..e76c03b4 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 b4054ef3..bdb618e9 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 f157f521..d219031e 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 c9524187..f6a8a292 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 b9a1dfee..3b8aad9c 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 55459a2d..c7dfcfe2 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 e76c03b4..e332f48b 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 bdb618e9..baa86663 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 00000000..03d2a41d --- /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 00000000..6f86404a --- /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 00000000..ec1c4aef --- /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 00000000..1876297d --- /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 00000000..97ddd04d --- /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 00000000..3fb3c916 --- /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 00000000..a0bcc947 --- /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 17bb88fa..92520072 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 00000000..5c0d3d9f --- /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 00000000..4932cb24 --- /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 00000000..aa7d1396 --- /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 00000000..3605e8cb --- /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 00000000..50323a91 --- /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 00000000..ec8ea118 --- /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 00000000..5b1696cb --- /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 00000000..807df068 --- /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 00000000..f3baa931 --- /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 00000000..dddc2b45 --- /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 00000000..3b911b28 --- /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 00000000..70c952a3 --- /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 00000000..b912b2be --- /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 00000000..4c9873c2 --- /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 00000000..0e4a2f2e --- /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 00000000..5729a867 --- /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 00000000..4be257c9 --- /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 00000000..bf8aa128 --- /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 00000000..f9a870a0 --- /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 00000000..4d674146 --- /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 00000000..b0e16ec7 --- /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 00000000..31d5d72d --- /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 00000000..0f1a3d89 --- /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 00000000..aef3725b --- /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 e332f48b..7ef684ee 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 baa86663..7d493b66 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 03d2a41d..1f8e5ab1 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 7ef684ee..3b6ec8e7 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 7d493b66..1bf456d4 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 6f86404a..8a897ca1 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 807df068..873bece8 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 3b911b28..3bf66f84 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 1f41ad59..fc84a002 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 00000000..1dfd789d --- /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 9449a74b..e848ffea 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 80a817b0..3b7005c0 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 2bbb3a7a..28958252 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 db9f9a87..74e74746 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 9db2e769..80e79d26 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 02fafda8..3e2f5131 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 3b6ec8e7..dd8420a3 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 1bf456d4..84c3d4a0 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 ec1ff849..2d49d72e 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 00000000..e78d5c71 --- /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 00000000..8e64bb1a --- /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 a367e8f6..9633ab35 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 cc191e6d..6aa4e3a0 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 57ca4092..ceca8406 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 08c3ee6d..00000000 --- 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 eff62bb0..12ac42e6 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 12ac42e6..a4012e2c 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 8e64bb1a..7518c650 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 18a79cfb..06c44cb2 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 e7d49112..9c8d1812 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 16c878cd..0d6c494f 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 0d6c494f..c719d434 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 9c8d1812..57c8f925 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 57c8f925..9c8d1812 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 9c8d1812..1269f26a 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 1269f26a..9c8d1812 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 d0351720..37b635f4 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 37b635f4..d0351720 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 d0351720..4e206ad4 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 e2b28b27..2eab5b35 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 4e206ad4..d0351720 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 9c8d1812..bcf2f0a3 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 7f423e04..7d0ee0c9 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 bcf2f0a3..9c8d1812 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 a8c6dcff..0113383b 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 7d0ee0c9..7f423e04 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 0113383b..e9166dc0 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 e9166dc0..a8c6dcff 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 d0351720..c982effa 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 a8c6dcff..e9166dc0 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 c982effa..d0351720 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 03432c9a..33f21b46 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 6e932a61..3301066a 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 7f423e04..c100d26c 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 8c5dd2f5..63f598d5 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 55b1da69..0820858b 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 9c8d1812..60b77259 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 331fb280..fa7d1002 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 e9f06798..329f57d4 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 7cdde5be..2a1da337 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 1f066351..4e7ec6a0 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 60b77259..5f34e308 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 5f34e308..f5b1730f 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 f5b1730f..12496218 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 12496218..990fa8a2 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 43aa36d0..b4e87927 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 4c838420..6b8b2408 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 a4012e2c..a192b2cf 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 e9166dc0..a8c6dcff 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 bc5aaee1..6401a739 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 f6a8a292..3c9e28a4 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 a192b2cf..e45d21fc 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 c100d26c..ba95b6ad 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 990fa8a2..2d2ba346 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 fa7d1002..ec38de80 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 329f57d4..8cffd859 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 2a1da337..36e81ff6 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 3c9e28a4..e3b915fb 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 e45d21fc..a192b2cf 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 ba95b6ad..c100d26c 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 2d2ba346..990fa8a2 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 ec38de80..fa7d1002 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 8cffd859..329f57d4 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 36e81ff6..2a1da337 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 e3b915fb..3c9e28a4 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 a192b2cf..e45d21fc 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 c100d26c..d31d3941 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 3c9e28a4..e3b915fb 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