From 6482caf1f056b5d7aaf027e54e5079a3834e77b1 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 20 Jul 2025 14:15:44 +0200 Subject: [PATCH 1/7] Added support for std::span --- include/rfl/parsing/Parser.hpp | 3 +- include/rfl/parsing/Parser_span.hpp | 70 +++++++++++++++++++++++++++++ tests/json/test_span_read_write.cpp | 56 +++++++++++++++++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 include/rfl/parsing/Parser_span.hpp create mode 100644 tests/json/test_span_read_write.cpp diff --git a/include/rfl/parsing/Parser.hpp b/include/rfl/parsing/Parser.hpp index a1a9c8a7..c7a1f9fd 100644 --- a/include/rfl/parsing/Parser.hpp +++ b/include/rfl/parsing/Parser.hpp @@ -23,13 +23,14 @@ #include "Parser_rfl_variant.hpp" #include "Parser_shared_ptr.hpp" #include "Parser_skip.hpp" +#include "Parser_span.hpp" #include "Parser_string_view.hpp" #include "Parser_tagged_union.hpp" #include "Parser_tuple.hpp" #include "Parser_unique_ptr.hpp" #include "Parser_variant.hpp" -#include "Parser_vectorstring.hpp" #include "Parser_vector_like.hpp" +#include "Parser_vectorstring.hpp" #include "Parser_wstring.hpp" #endif diff --git a/include/rfl/parsing/Parser_span.hpp b/include/rfl/parsing/Parser_span.hpp new file mode 100644 index 00000000..1b709b0d --- /dev/null +++ b/include/rfl/parsing/Parser_span.hpp @@ -0,0 +1,70 @@ +#ifndef RFL_PARSING_PARSER_SPAN_HPP_ +#define RFL_PARSING_PARSER_SPAN_HPP_ + +#include +#include +#include +#include +#include +#include + +#include "../Result.hpp" +#include "../always_false.hpp" +#include "Parent.hpp" +#include "Parser_base.hpp" +#include "schema/Type.hpp" + +namespace rfl::parsing { + +template + requires AreReaderAndWriter> +struct Parser, ProcessorsType> { + using InputVarType = typename R::InputVarType; + using ParentType = Parent; + + static Result> read(const R& _r, + const InputVarType& _var) noexcept { + if constexpr (!ProcessorsType::allow_raw_ptrs_) { + static_assert(always_false_v, + "Reading into std::span is dangerous and " + "therefore unsupported. " + "Please consider using std::vector instead, or use the " + "rfl::AllowRawPtrs processor."); + return error("Unsupported."); + } else { + return Parser>, + ProcessorsType>::read(_r, _var) + .transform([](std::vector&& _vec) { + using Type = std::remove_cvref_t; + Type* data = new Type[_vec.size()]; + for (size_t i = 0; i < _vec.size(); ++i) { + data[i] = std::move(_vec[i]); + } + return std::span(data, data + _vec.size()); + }); + } + } + + template + static void write(const W& _w, const std::span& _span, + const P& _parent) noexcept { + auto arr = ParentType::add_array( + _w, std::distance(_span.begin(), _span.end()), _parent); + const auto new_parent = typename ParentType::Array{&arr}; + for (const auto& v : _span) { + Parser, ProcessorsType>::write(_w, v, + new_parent); + } + _w.end_array(&arr); + } + + static schema::Type to_schema( + std::map* _definitions) { + return Parser>, + ProcessorsType>::to_schema(_definitions); + } +}; + +} // namespace rfl::parsing + +#endif diff --git a/tests/json/test_span_read_write.cpp b/tests/json/test_span_read_write.cpp new file mode 100644 index 00000000..ab3a1241 --- /dev/null +++ b/tests/json/test_span_read_write.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_span { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::span children; +}; + +TEST(json, test_span) { + auto children = std::vector({Person{.first_name = "Bart"}, + Person{.first_name = "Lisa"}, + Person{.first_name = "Maggie"}}); + + const auto homer = + Person{.first_name = "Homer", + .children = std::span(children.data() + 1, + children.data() + children.size())}; + + const auto json_string = rfl::json::write(homer); + + const std::string expected = + R"({"firstName":"Homer","lastName":"Simpson","children":[{"firstName":"Lisa","lastName":"Simpson","children":[]},{"firstName":"Maggie","lastName":"Simpson","children":[]}]})"; + + const auto json_string1 = rfl::json::write(homer); + EXPECT_EQ(json_string1, expected) + << "Test failed on write. Expected:" << std::endl + << expected << std::endl + << "Got: " << std::endl + << json_string1 << std::endl + << std::endl; + const auto res = rfl::json::read(json_string1); + EXPECT_TRUE(res && true) << "Test failed on read. Error: " + << res.error().what(); + const auto json_string2 = rfl::json::write(res.value()); + EXPECT_EQ(json_string2, expected) + << "Test failed on read. Expected:" << std::endl + << expected << std::endl + << "Got: " << std::endl + << json_string2 << std::endl + << std::endl; + + if (res) { + delete[] res->children.data(); + } +} +} // namespace test_span From cd2a9e2f11aa52d8f85096cecf35acd6dec18673 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 20 Jul 2025 14:33:47 +0200 Subject: [PATCH 2/7] Added documentation --- README.md | 2 ++ docs/concepts/processors.md | 13 +++++++++++-- include/rfl/parsing/Parser_span.hpp | 16 +++++++++++----- include/rfl/parsing/Parser_string_view.hpp | 16 +++++++++++----- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 038af9b7..a5404c02 100644 --- a/README.md +++ b/README.md @@ -500,7 +500,9 @@ reflect-cpp supports the following containers from the C++ standard library: - `std::pair` - `std::set` - `std::shared_ptr` +- `std::span` - `std::string` +- `std::string_view` - `std::tuple` - `std::unique_ptr` - `std::unordered_map` diff --git a/docs/concepts/processors.md b/docs/concepts/processors.md index 12a7a6d2..f07b24d7 100644 --- a/docs/concepts/processors.md +++ b/docs/concepts/processors.md @@ -119,10 +119,13 @@ Please refer to the relevant sections of the documentation. ### `rfl::AllowRawPtrs` -By default, reflect-cpp does not allow *reading into* raw pointers. (*Writing from* raw pointers is never a problem.) This is because reading into raw pointers means that the library will allocate memory that the user then has to manually delete. This can lead to misunderstandings and memory leaks. +By default, reflect-cpp does not allow *reading into* raw pointers, `std::string_view` or `std::span`. +(*Writing from* raw pointers is never a problem.) This is because reading into raw pointers +means that the library will allocate memory that the user then has to manually delete. This can lead to misunderstandings and memory leaks. You might want to consider using some alternatives, such as `std::unique_ptr`, `rfl::Box`, -`std::shared_ptr`, `rfl::Ref` or `std::optional`. But if you absolutely have to use raw pointers, you can pass `rfl::AllowRawPtrs` to `read`: +`std::shared_ptr`, `rfl::Ref` or `std::optional`. +But if you absolutely have to use raw pointers, you can pass `rfl::AllowRawPtrs` to `read`: ```cpp struct Person { @@ -152,6 +155,12 @@ void delete_raw_pointers(const Person& _person) { delete_raw_pointers(person); ``` +`std::string_view` and `std::span` must be cleaned up using `delete[]`, like this: + +```cpp +delete[] person.string_view_or_span.data(); +``` + ### `rfl::DefaultIfMissing` The `rfl::DefaultIfMissing` processor is only relevant for reading data. For writing data, it will make no difference. diff --git a/include/rfl/parsing/Parser_span.hpp b/include/rfl/parsing/Parser_span.hpp index 1b709b0d..a4b8e9c0 100644 --- a/include/rfl/parsing/Parser_span.hpp +++ b/include/rfl/parsing/Parser_span.hpp @@ -25,11 +25,17 @@ struct Parser, ProcessorsType> { static Result> read(const R& _r, const InputVarType& _var) noexcept { if constexpr (!ProcessorsType::allow_raw_ptrs_) { - static_assert(always_false_v, - "Reading into std::span is dangerous and " - "therefore unsupported. " - "Please consider using std::vector instead, or use the " - "rfl::AllowRawPtrs processor."); + static_assert( + always_false_v, + "Reading into std::span is dangerous and " + "therefore unsupported. " + "Please consider using std::vector instead or wrapping " + "std::vector in rfl::Box or rfl::Ref." + "If you absolutely must use std::span, " + "you can pass the rfl::AllowRawPtrs processor. " + "Please note that it is then YOUR responsibility " + "to delete the allocated memory. Please also refer " + "to the related documentation (in the section on processors)."); return error("Unsupported."); } else { return Parser>, diff --git a/include/rfl/parsing/Parser_string_view.hpp b/include/rfl/parsing/Parser_string_view.hpp index 641c1701..492a3201 100644 --- a/include/rfl/parsing/Parser_string_view.hpp +++ b/include/rfl/parsing/Parser_string_view.hpp @@ -23,11 +23,17 @@ struct Parser { static Result read(const R& _r, const InputVarType& _var) noexcept { if constexpr (!ProcessorsType::allow_raw_ptrs_) { - static_assert(always_false_v, - "Reading into std::string_view is dangerous and " - "therefore unsupported. " - "Please consider using std::string instead, or use the " - "rfl::AllowRawPtrs processor."); + static_assert( + always_false_v, + "Reading into std::string_view is dangerous and " + "therefore unsupported. " + "Please consider using std::string instead or wrapping " + "std::string in rfl::Box or rfl::Ref." + "If you absolutely must use std::string_view, " + "you can pass the rfl::AllowRawPtrs processor. " + "Please note that it is then YOUR responsibility " + "to delete the allocated memory. Please also refer " + "to the related documentation (in the section on processors)."); return error("Unsupported."); } else { return Parser::read(_r, _var) From 9747d4c737ca0d4e9cb612c257d1f3b7b0f68dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dr=2E=20Patrick=20Urbanke=20=28=E5=8A=89=E8=87=AA=E6=88=90?= =?UTF-8?q?=29?= Date: Sun, 20 Jul 2025 14:42:30 +0200 Subject: [PATCH 3/7] Use _span.size() Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- include/rfl/parsing/Parser_span.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rfl/parsing/Parser_span.hpp b/include/rfl/parsing/Parser_span.hpp index a4b8e9c0..8fdf37f8 100644 --- a/include/rfl/parsing/Parser_span.hpp +++ b/include/rfl/parsing/Parser_span.hpp @@ -55,7 +55,7 @@ struct Parser, ProcessorsType> { static void write(const W& _w, const std::span& _span, const P& _parent) noexcept { auto arr = ParentType::add_array( - _w, std::distance(_span.begin(), _span.end()), _parent); + _w, _span.size(), _parent); const auto new_parent = typename ParentType::Array{&arr}; for (const auto& v : _span) { Parser, ProcessorsType>::write(_w, v, From 018777bb2f23ed50d37c65a2da7763b78d65e303 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 20 Jul 2025 14:57:34 +0200 Subject: [PATCH 4/7] Fixes in test_span --- .../{test_span_read_write.cpp => test_span.cpp} | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) rename tests/json/{test_span_read_write.cpp => test_span.cpp} (80%) diff --git a/tests/json/test_span_read_write.cpp b/tests/json/test_span.cpp similarity index 80% rename from tests/json/test_span_read_write.cpp rename to tests/json/test_span.cpp index ab3a1241..534d0a12 100644 --- a/tests/json/test_span_read_write.cpp +++ b/tests/json/test_span.cpp @@ -13,7 +13,16 @@ namespace test_span { struct Person { rfl::Rename<"firstName", std::string> first_name; rfl::Rename<"lastName", std::string> last_name = "Simpson"; - std::span children; + rfl::Ref> children; +}; + +void delete_children(const Person& _p) { + for (const auto& child : *_p.children) { + delete_children(child); + } + if (!_p.children->empty()) { + delete[] _p.children->data(); + } }; TEST(json, test_span) { @@ -23,8 +32,8 @@ TEST(json, test_span) { const auto homer = Person{.first_name = "Homer", - .children = std::span(children.data() + 1, - children.data() + children.size())}; + .children = rfl::Ref>::make( + children.data() + 1, children.data() + children.size())}; const auto json_string = rfl::json::write(homer); @@ -50,7 +59,7 @@ TEST(json, test_span) { << std::endl; if (res) { - delete[] res->children.data(); + delete_children(*res); } } } // namespace test_span From 5599caa83b51577ac7c487cf9dacf7a8eaf62ad1 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 20 Jul 2025 15:02:30 +0200 Subject: [PATCH 5/7] Use std::nothrow --- include/rfl/parsing/Parser_span.hpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/rfl/parsing/Parser_span.hpp b/include/rfl/parsing/Parser_span.hpp index 8fdf37f8..68d1b22b 100644 --- a/include/rfl/parsing/Parser_span.hpp +++ b/include/rfl/parsing/Parser_span.hpp @@ -42,7 +42,10 @@ struct Parser, ProcessorsType> { ProcessorsType>::read(_r, _var) .transform([](std::vector&& _vec) { using Type = std::remove_cvref_t; - Type* data = new Type[_vec.size()]; + Type* data = new (std::nothrow) Type[_vec.size()]; + if (!data) { + return error("Failed to allocate memory for std::span."); + } for (size_t i = 0; i < _vec.size(); ++i) { data[i] = std::move(_vec[i]); } @@ -54,8 +57,7 @@ struct Parser, ProcessorsType> { template static void write(const W& _w, const std::span& _span, const P& _parent) noexcept { - auto arr = ParentType::add_array( - _w, _span.size(), _parent); + auto arr = ParentType::add_array(_w, _span.size(), _parent); const auto new_parent = typename ParentType::Array{&arr}; for (const auto& v : _span) { Parser, ProcessorsType>::write(_w, v, From 3c68945dc7b732d0667255f6bb854aa0877f54dc Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 20 Jul 2025 15:09:21 +0200 Subject: [PATCH 6/7] Better example for deleting span --- docs/concepts/processors.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/concepts/processors.md b/docs/concepts/processors.md index f07b24d7..32e29586 100644 --- a/docs/concepts/processors.md +++ b/docs/concepts/processors.md @@ -158,7 +158,11 @@ delete_raw_pointers(person); `std::string_view` and `std::span` must be cleaned up using `delete[]`, like this: ```cpp -delete[] person.string_view_or_span.data(); +delete[] person.string_view.data(); + +if(!person.span.empty()) { + delete[] person.span.data(); +} ``` ### `rfl::DefaultIfMissing` From 63094509286d113ce513b0b72782e5078cf4ecf1 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 20 Jul 2025 15:26:43 +0200 Subject: [PATCH 7/7] Use and_then --- include/rfl/parsing/Parser_span.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rfl/parsing/Parser_span.hpp b/include/rfl/parsing/Parser_span.hpp index 68d1b22b..e8e2f7a1 100644 --- a/include/rfl/parsing/Parser_span.hpp +++ b/include/rfl/parsing/Parser_span.hpp @@ -40,7 +40,7 @@ struct Parser, ProcessorsType> { } else { return Parser>, ProcessorsType>::read(_r, _var) - .transform([](std::vector&& _vec) { + .and_then([](std::vector&& _vec) -> Result> { using Type = std::remove_cvref_t; Type* data = new (std::nothrow) Type[_vec.size()]; if (!data) {