Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
17 changes: 15 additions & 2 deletions docs/concepts/processors.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion include/rfl/parsing/Parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
78 changes: 78 additions & 0 deletions include/rfl/parsing/Parser_span.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#ifndef RFL_PARSING_PARSER_SPAN_HPP_
#define RFL_PARSING_PARSER_SPAN_HPP_

#include <cstring>
#include <map>
#include <span>
#include <string>
#include <type_traits>
#include <vector>

#include "../Result.hpp"
#include "../always_false.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"

namespace rfl::parsing {

template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::span<T>>
struct Parser<R, W, std::span<T>, ProcessorsType> {
using InputVarType = typename R::InputVarType;
using ParentType = Parent<W>;

static Result<std::span<T>> read(const R& _r,
const InputVarType& _var) noexcept {
if constexpr (!ProcessorsType::allow_raw_ptrs_) {
static_assert(
always_false_v<R>,
"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<R, W, std::vector<std::remove_cvref_t<T>>,
ProcessorsType>::read(_r, _var)
.and_then([](std::vector<T>&& _vec) -> Result<std::span<T>> {
using Type = std::remove_cvref_t<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<T>(data, data + _vec.size());
});
}
}

template <class P>
static void write(const W& _w, const std::span<T>& _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<R, W, std::remove_cvref_t<T>, ProcessorsType>::write(_w, v,
new_parent);
}
_w.end_array(&arr);
}

static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
return Parser<R, W, std::vector<std::remove_cvref_t<T>>,
ProcessorsType>::to_schema(_definitions);
}
};

} // namespace rfl::parsing

#endif
16 changes: 11 additions & 5 deletions include/rfl/parsing/Parser_string_view.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,17 @@ struct Parser<R, W, std::string_view, ProcessorsType> {
static Result<std::string_view> read(const R& _r,
const InputVarType& _var) noexcept {
if constexpr (!ProcessorsType::allow_raw_ptrs_) {
static_assert(always_false_v<R>,
"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<R>,
"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<R, W, std::string, ProcessorsType>::read(_r, _var)
Expand Down
65 changes: 65 additions & 0 deletions tests/json/test_span.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#include <iostream>
#include <memory>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <span>
#include <string>
#include <vector>

#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<std::span<Person>> 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>({Person{.first_name = "Bart"},
Person{.first_name = "Lisa"},
Person{.first_name = "Maggie"}});

const auto homer =
Person{.first_name = "Homer",
.children = rfl::Ref<std::span<Person>>::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<Person, rfl::AllowRawPtrs>(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
Loading