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..32e29586 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,16 @@ 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.data(); + +if(!person.span.empty()) { + delete[] person.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.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..e8e2f7a1 --- /dev/null +++ b/include/rfl/parsing/Parser_span.hpp @@ -0,0 +1,78 @@ +#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 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>, + ProcessorsType>::read(_r, _var) + .and_then([](std::vector&& _vec) -> Result> { + using Type = std::remove_cvref_t; + 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]); + } + 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, _span.size(), _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/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) diff --git a/tests/json/test_span.cpp b/tests/json/test_span.cpp new file mode 100644 index 00000000..534d0a12 --- /dev/null +++ b/tests/json/test_span.cpp @@ -0,0 +1,65 @@ +#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"; + 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) { + auto children = std::vector({Person{.first_name = "Bart"}, + Person{.first_name = "Lisa"}, + Person{.first_name = "Maggie"}}); + + const auto homer = + Person{.first_name = "Homer", + .children = rfl::Ref>::make( + 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_children(*res); + } +} +} // namespace test_span