Skip to content

Commit 9fe3758

Browse files
Automatically add tags to variants; fixes #230 (#246)
1 parent 2ef144c commit 9fe3758

13 files changed

+299
-11
lines changed

docs/processors.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ The resulting JSON string looks like this:
3434
reflect-cpp currently supports the following processors:
3535

3636
- `rfl::AddStructName`
37+
- `rfl::AddTagsToVariants`
3738
- `rfl::AllowRawPtrs`
3839
- `rfl::DefaultIfMissing`
3940
- `rfl::NoExtraFields`
@@ -61,6 +62,58 @@ The resulting JSON string looks like this:
6162
{"type":"Person","first_name":"Homer","last_name":"Simpson","age":45}
6263
```
6364

65+
### `rfl::AddStructsToVariants`
66+
67+
This processor automatically adds structs to variants. Consider the following example:
68+
69+
```cpp
70+
struct button_pressed_t {};
71+
72+
struct button_released_t {};
73+
74+
struct key_pressed_t {
75+
char key;
76+
};
77+
78+
using my_event_type_t =
79+
std::variant<button_pressed_t, button_released_t, key_pressed_t, int>;
80+
```
81+
82+
The problem here is that `button_pressed_t` and `butten_released_t` virtually look
83+
indistinguishable. The will both be serialized to `{}`.
84+
85+
But you can add this processor to automatically add tags and avoid the problem:
86+
87+
```cpp
88+
const auto vec = std::vector<my_event_type_t>(
89+
{button_pressed_t{}, button_released_t{}, key_pressed_t{'c'}, 3});
90+
91+
const auto json_string = rfl::json::write<rfl::AddTagsToVariants>(vec);
92+
93+
rfl::json::write<std::vector<my_event_type_t>, rfl::AddTagsToVariants>(json_string);
94+
```
95+
96+
`vec` will now be serialized as follows:
97+
98+
```json
99+
[{"button_pressed_t":{}},{"button_released_t":{}},{"key_pressed_t":{"key":99}},{"int":3}]
100+
```
101+
102+
You can also set your own custom tags like this:
103+
104+
```cpp
105+
struct key_pressed_t {
106+
using Tag = rfl::Literal<"your_custom_tag">;
107+
char key;
108+
};
109+
```
110+
111+
`key_pressed_t` will now be serialized as follows:
112+
113+
```json
114+
{"your_custom_tag":{"key":99}}
115+
```
116+
64117
### `rfl::AllowRawPtrs`
65118

66119
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.

docs/variants_and_tagged_unions.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,21 @@ several problems with this:
4545
2) It leads to confusing error messages: If none of the alternatives can be matched, you will get an error message telling you why each of the alternatives couldn't be matched. Such error messages are very long-winding and hard to read.
4646
3) It is dangerous. Imagine we had written `rfl::Variant<Circle, Square, Rectangle>` instead of `rfl::Variant<Circle, Rectangle, Square>`. This would mean that `Rectangle` could never be matched, because the fields in `Square` are a subset of `Rectangle`. This leads to very confusing bugs.
4747
48+
## Automatic tags
49+
50+
The easiest way to solve this problem is to simply add tags automatically. You can do so by using `rfl::AddTagsToVariants`:
51+
52+
```cpp
53+
const auto json_string = rfl::json::write<rfl::AddTagsToVariants>(r);
54+
55+
const auto r2 = rfl::json::read<Shapes, rfl::AddTagsToVariants>(json_string);
56+
```
57+
58+
Please refer to the section on processors in this documentation for more information.
59+
4860
## `rfl::TaggedUnion` (internally tagged)
4961

50-
One way to solve this problem is to add a tag inside the class. That is why we have provided a helper class for these purposes: `rfl::TaggedUnion`.
62+
Another way to solve this problem is to add a tag inside the class. That is why we have provided a helper class for these purposes: `rfl::TaggedUnion`.
5163

5264
TaggedUnions use the name of the struct as an identifying tag. It will then try to take that field from the JSON object, match it to the correct alternative and then only parse the correct alternative.
5365

include/rfl.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#endif
99

1010
#include "rfl/AddStructName.hpp"
11+
#include "rfl/AddTagsToVariants.hpp"
1112
#include "rfl/AllOf.hpp"
1213
#include "rfl/AllowRawPtrs.hpp"
1314
#include "rfl/AnyOf.hpp"

include/rfl/AddTagsToVariants.hpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#ifndef RFL_ADDTAGSTOVARIANTS_HPP_
2+
#define RFL_ADDTAGSTOVARIANTS_HPP_
3+
4+
namespace rfl {
5+
6+
/// This is a "fake" processor - it doesn't do much in itself, but its
7+
/// inclusion instructs the parsers to automatically add tags to the variants
8+
/// they might encounter.
9+
struct AddTagsToVariants {
10+
public:
11+
template <class StructType>
12+
static auto process(auto&& _named_tuple) {
13+
return _named_tuple;
14+
}
15+
};
16+
17+
} // namespace rfl
18+
19+
#endif

include/rfl/Processors.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <type_traits>
55

6+
#include "internal/is_add_tags_to_variants_v.hpp"
67
#include "internal/is_allow_raw_ptrs_v.hpp"
78
#include "internal/is_default_if_missing_v.hpp"
89
#include "internal/is_no_extra_fields_v.hpp"
@@ -17,6 +18,7 @@ struct Processors;
1718

1819
template <>
1920
struct Processors<> {
21+
static constexpr bool add_tags_to_variants_ = false;
2022
static constexpr bool allow_raw_ptrs_ = false;
2123
static constexpr bool all_required_ = false;
2224
static constexpr bool default_if_missing_ = false;
@@ -32,6 +34,10 @@ struct Processors<> {
3234

3335
template <class Head, class... Tail>
3436
struct Processors<Head, Tail...> {
37+
static constexpr bool add_tags_to_variants_ =
38+
std::disjunction_v<internal::is_add_tags_to_variants<Head>,
39+
internal::is_add_tags_to_variants<Tail>...>;
40+
3541
static constexpr bool allow_raw_ptrs_ =
3642
std::disjunction_v<internal::is_allow_raw_ptrs<Head>,
3743
internal::is_allow_raw_ptrs<Tail>...>;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#ifndef RFL_INTERNAL_ISADDTAGSTOVARIANTS_HPP_
2+
#define RFL_INTERNAL_ISADDTAGSTOVARIANTS_HPP_
3+
4+
#include <tuple>
5+
#include <type_traits>
6+
#include <utility>
7+
8+
#include "../AddTagsToVariants.hpp"
9+
10+
namespace rfl ::internal {
11+
12+
template <class T>
13+
class is_add_tags_to_variants;
14+
15+
template <class T>
16+
class is_add_tags_to_variants : public std::false_type {};
17+
18+
template <>
19+
class is_add_tags_to_variants<AddTagsToVariants> : public std::true_type {};
20+
21+
template <class T>
22+
constexpr bool is_add_tags_to_variants_v = is_add_tags_to_variants<
23+
std::remove_cvref_t<std::remove_pointer_t<T>>>::value;
24+
25+
} // namespace rfl::internal
26+
27+
#endif

include/rfl/internal/tag_t.hpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
namespace rfl::internal {
1010

1111
template <internal::StringLiteral _discriminator, class T>
12-
using tag_t =
13-
typename std::invoke_result<decltype(make_tag<_discriminator, T>), T>::type;
12+
using tag_t = std::invoke_result_t<decltype(make_tag<_discriminator, T>), T>;
1413

1514
} // namespace rfl::internal
1615

include/rfl/parsing/FieldVariantParser.hpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,12 @@ struct FieldVariantParser {
6464
"Externally tagged variants cannot have duplicate field "
6565
"names.");
6666

67-
const auto handle = [&](const auto& _field) {
67+
_v.visit([&](const auto& _field) {
6868
const auto named_tuple = make_named_tuple(internal::to_ptr_field(_field));
6969
using NamedTupleType = std::remove_cvref_t<decltype(named_tuple)>;
7070
Parser<R, W, NamedTupleType, ProcessorsType>::write(_w, named_tuple,
7171
_parent);
72-
};
73-
74-
rfl::visit(handle, _v);
72+
});
7573
}
7674

7775
static schema::Type to_schema(

include/rfl/parsing/Parser_rfl_variant.hpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "../internal/nth_element_t.hpp"
1212
#include "FieldVariantParser.hpp"
1313
#include "Parser_base.hpp"
14+
#include "VariantAlternativeWrapper.hpp"
1415
#include "schema/Type.hpp"
1516

1617
namespace rfl::parsing {
@@ -26,6 +27,19 @@ class Parser<R, W, rfl::Variant<AlternativeTypes...>, ProcessorsType> {
2627
if constexpr (internal::all_fields<std::tuple<AlternativeTypes...>>()) {
2728
return FieldVariantParser<R, W, ProcessorsType,
2829
AlternativeTypes...>::read(_r, _var);
30+
31+
} else if constexpr (ProcessorsType::add_tags_to_variants_) {
32+
using FieldVariantType =
33+
rfl::Variant<VariantAlternativeWrapper<AlternativeTypes>...>;
34+
const auto from_field_variant =
35+
[](const auto& _field) -> rfl::Variant<AlternativeTypes...> {
36+
return std::move(_field.value());
37+
};
38+
return Parser<R, W, FieldVariantType, ProcessorsType>::read(_r, _var)
39+
.transform([&](FieldVariantType&& _f) {
40+
return _f.visit(from_field_variant);
41+
});
42+
2943
} else {
3044
std::optional<rfl::Variant<AlternativeTypes...>> result;
3145
std::vector<Error> errors;
@@ -52,6 +66,17 @@ class Parser<R, W, rfl::Variant<AlternativeTypes...>, ProcessorsType> {
5266
if constexpr (internal::all_fields<std::tuple<AlternativeTypes...>>()) {
5367
FieldVariantParser<R, W, ProcessorsType, AlternativeTypes...>::write(
5468
_w, _variant, _parent);
69+
70+
} else if constexpr (ProcessorsType::add_tags_to_variants_) {
71+
using FieldVariantType =
72+
rfl::Variant<VariantAlternativeWrapper<const AlternativeTypes*>...>;
73+
const auto to_field_variant =
74+
[]<class T>(const T& _t) -> FieldVariantType {
75+
return VariantAlternativeWrapper<const T*>(&_t);
76+
};
77+
Parser<R, W, FieldVariantType, ProcessorsType>::write(
78+
_w, _variant.visit(to_field_variant), _parent);
79+
5580
} else {
5681
const auto handle = [&](const auto& _v) {
5782
using Type = std::remove_cvref_t<decltype(_v)>;
@@ -66,6 +91,13 @@ class Parser<R, W, rfl::Variant<AlternativeTypes...>, ProcessorsType> {
6691
if constexpr (internal::all_fields<std::tuple<AlternativeTypes...>>()) {
6792
return FieldVariantParser<R, W, ProcessorsType,
6893
AlternativeTypes...>::to_schema(_definitions);
94+
95+
} else if constexpr (ProcessorsType::add_tags_to_variants_) {
96+
using FieldVariantType =
97+
rfl::Variant<VariantAlternativeWrapper<AlternativeTypes>...>;
98+
return Parser<R, W, FieldVariantType, ProcessorsType>::to_schema(
99+
_definitions);
100+
69101
} else {
70102
std::vector<schema::Type> types;
71103
build_schema(

include/rfl/parsing/Parser_variant.hpp

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
#include "../internal/to_ptr_field.hpp"
1414
#include "FieldVariantParser.hpp"
1515
#include "Parser_base.hpp"
16+
#include "VariantAlternativeWrapper.hpp"
1617
#include "schema/Type.hpp"
1718
#include "to_single_error_message.hpp"
1819

19-
namespace rfl {
20-
namespace parsing {
20+
namespace rfl::parsing {
2121

2222
template <class R, class W, class... AlternativeTypes, class ProcessorsType>
2323
requires AreReaderAndWriter<R, W, std::variant<AlternativeTypes...>>
@@ -41,6 +41,19 @@ class Parser<R, W, std::variant<AlternativeTypes...>, ProcessorsType> {
4141
return FieldVariantParser<R, W, ProcessorsType,
4242
AlternativeTypes...>::read(_r, _var)
4343
.transform(to_std_variant);
44+
45+
} else if constexpr (ProcessorsType::add_tags_to_variants_) {
46+
using FieldVariantType =
47+
rfl::Variant<VariantAlternativeWrapper<AlternativeTypes>...>;
48+
const auto from_field_variant =
49+
[](auto&& _field) -> std::variant<AlternativeTypes...> {
50+
return std::move(_field.value());
51+
};
52+
return Parser<R, W, FieldVariantType, ProcessorsType>::read(_r, _var)
53+
.transform([&](FieldVariantType&& _f) {
54+
return _f.visit(from_field_variant);
55+
});
56+
4457
} else {
4558
std::optional<std::variant<AlternativeTypes...>> result;
4659
std::vector<Error> errors;
@@ -76,6 +89,16 @@ class Parser<R, W, std::variant<AlternativeTypes...>, ProcessorsType> {
7689
R, W, ProcessorsType,
7790
ptr_field_t<AlternativeTypes>...>::write(_w, to_rfl_variant(_variant),
7891
_parent);
92+
} else if constexpr (ProcessorsType::add_tags_to_variants_) {
93+
using FieldVariantType =
94+
rfl::Variant<VariantAlternativeWrapper<const AlternativeTypes*>...>;
95+
const auto to_field_variant =
96+
[]<class T>(const T& _t) -> FieldVariantType {
97+
return VariantAlternativeWrapper<const T*>(&_t);
98+
};
99+
Parser<R, W, FieldVariantType, ProcessorsType>::write(
100+
_w, std::visit(to_field_variant, _variant), _parent);
101+
79102
} else {
80103
const auto handle = [&](const auto& _v) {
81104
using Type = std::remove_cvref_t<decltype(_v)>;
@@ -90,6 +113,13 @@ class Parser<R, W, std::variant<AlternativeTypes...>, ProcessorsType> {
90113
if constexpr (internal::all_fields<std::tuple<AlternativeTypes...>>()) {
91114
return FieldVariantParser<R, W, ProcessorsType,
92115
AlternativeTypes...>::to_schema(_definitions);
116+
117+
} else if constexpr (ProcessorsType::add_tags_to_variants_) {
118+
using FieldVariantType =
119+
rfl::Variant<VariantAlternativeWrapper<AlternativeTypes>...>;
120+
return Parser<R, W, FieldVariantType, ProcessorsType>::to_schema(
121+
_definitions);
122+
93123
} else {
94124
std::vector<schema::Type> types;
95125
build_schema(
@@ -142,7 +172,6 @@ class Parser<R, W, std::variant<AlternativeTypes...>, ProcessorsType> {
142172
}
143173
};
144174

145-
} // namespace parsing
146-
} // namespace rfl
175+
} // namespace rfl::parsing
147176

148177
#endif

0 commit comments

Comments
 (0)