Skip to content

Commit d33a4f5

Browse files
authored
Add rfl::AddNamespacedTagsToVariants for rfl::Generic and same-name-different-namespace structs (getml#494)
1 parent 483849d commit d33a4f5

20 files changed

Lines changed: 861 additions & 120 deletions

docs/concepts/processors.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ reflect-cpp currently supports the following processors:
3535

3636
- `rfl::AddStructName`
3737
- `rfl::AddTagsToVariants`
38+
- `rfl::AddNamespacedTagsToVariants`
3839
- `rfl::AllowRawPtrs`
3940
- `rfl::DefaultIfMissing`
4041
- `rfl::NoExtraFields`
@@ -117,6 +118,89 @@ struct key_pressed_t {
117118
Note that there are other ways to address problems like this, for instance `rfl::TaggedUnion`.
118119
Please refer to the relevant sections of the documentation.
119120

121+
### `rfl::AddNamespacedTagsToVariants`
122+
123+
This processor is similar to `rfl::AddTagsToVariants`, but instead of using just the struct name as the tag, it uses the full namespaced type name. This is particularly useful when you have:
124+
125+
1. Structs with the same name in different namespaces
126+
2. Structs with `rfl::Generic` fields that would otherwise create naming conflicts
127+
128+
#### Use-case: Structs with the same name in different namespaces
129+
130+
Consider this example where `rfl::AddTagsToVariants` would fail:
131+
132+
```cpp
133+
namespace Result {
134+
struct Message {
135+
std::string result;
136+
};
137+
}
138+
139+
namespace Error {
140+
struct Message {
141+
std::string error;
142+
int error_id;
143+
};
144+
}
145+
146+
using Messages = std::variant<Result::Message, Error::Message>;
147+
148+
const auto msgs = std::vector<Messages>{
149+
Result::Message{.result = "success"},
150+
Error::Message{.error = "failure", .error_id = 404}
151+
};
152+
153+
// This would cause problems with rfl::AddTagsToVariants because both
154+
// structs have the same name "Message"
155+
156+
// But this works perfectly:
157+
const auto json_string = rfl::json::write<rfl::AddNamespacedTagsToVariants>(msgs);
158+
const auto msgs2 = rfl::json::read<std::vector<Messages>, rfl::AddNamespacedTagsToVariants>(json_string);
159+
```
160+
161+
The resulting JSON includes the full namespace path:
162+
163+
```json
164+
[
165+
{"Result::Message": {"result": "success"}},
166+
{"Error::Message": {"error": "failure", "error_id": 404}}
167+
]
168+
```
169+
170+
#### Use-case: Structs with `rfl::Generic` fields that would otherwise create naming conflicts
171+
172+
Another use case is with `rfl::Generic` fields, where multiple structs contain generic fields that would otherwise create naming conflicts:
173+
174+
```cpp
175+
struct APIResult {
176+
rfl::Generic result; // Could be string, number, object, etc.
177+
};
178+
179+
struct APIError {
180+
rfl::Generic error; // Could be string, number, object, etc.
181+
};
182+
183+
using APIResponse = std::variant<APIResult, APIError>;
184+
185+
const auto response = APIResult{.result = std::string("200")};
186+
187+
// Without namespaces, both Generic fields would have the same tag name.
188+
// With namespaces, they get unique identifiers:
189+
const auto json_string = rfl::json::write<rfl::AddNamespacedTagsToVariants>(response);
190+
```
191+
192+
This generates:
193+
194+
```json
195+
{"APIResult": {"result": {"std::string": "200"}}}
196+
```
197+
198+
#### When to use `rfl::AddNamespacedTagsToVariants` vs `rfl::AddTagsToVariants`
199+
200+
- Use `rfl::AddTagsToVariants` when struct names are unique and you want shorter, cleaner tags
201+
- Use `rfl::AddNamespacedTagsToVariants` when you have naming conflicts or need to distinguish between types in different namespaces
202+
- Custom tags (using `Tag = rfl::Literal<"custom_name">`) work with both processors and are not affected by the namespace handling
203+
120204
### `rfl::AllowRawPtrs`
121205

122206
By default, reflect-cpp does not allow *reading into* raw pointers, `std::string_view` or `std::span`.

docs/variants_and_tagged_unions.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,44 @@ const auto r2 = rfl::json::read<Shapes, rfl::AddTagsToVariants>(json_string);
5757

5858
Please refer to the section on processors in this documentation for more information.
5959

60+
## Automatic tags with full namespaces
61+
62+
In some cases, you might have struct name conflicts between different namespaces, or types that would generate identical tags when namespaces are removed. For these situations, you can use `rfl::AddNamespacedTagsToVariants` instead:
63+
64+
```cpp
65+
namespace Result {
66+
struct Message {
67+
std::string result;
68+
};
69+
}
70+
71+
namespace Error {
72+
struct Message {
73+
std::string error;
74+
int error_id;
75+
};
76+
}
77+
78+
using Messages = std::variant<Result::Message, Error::Message>;
79+
80+
const Messages msg = Error::Message{.error = "Something went wrong", .error_id = 404};
81+
82+
const auto json_string = rfl::json::write<rfl::AddNamespacedTagsToVariants>(msg);
83+
```
84+
85+
This generates JSON with fully qualified type names:
86+
87+
```json
88+
{"Error::Message":{"error":"Something went wrong","error_id":404}}
89+
```
90+
91+
The key differences between `rfl::AddTagsToVariants` and `rfl::AddNamespacedTagsToVariants`:
92+
93+
- `rfl::AddTagsToVariants` uses just the struct name (e.g., `"Message"`)
94+
- `rfl::AddNamespacedTagsToVariants` uses the full namespaced name (e.g., `"Error::Message"`)
95+
- Both respect custom tags defined with `using Tag = rfl::Literal<"custom_name">`
96+
- Use the namespaced version when you are using `rfl::Generic` or need to distinguish between types in different namespaces and don't want to manually tag them as above
97+
6098
## `rfl::TaggedUnion` (internally tagged)
6199

62100
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`.

include/rfl/AddTagsToVariants.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@ struct AddTagsToVariants {
1414
}
1515
};
1616

17+
/// This is a "fake" processor - it doesn't do much in itself, but its
18+
/// inclusion instructs the parsers to automatically add tags to the variants
19+
/// they might encounter.
20+
struct AddNamespacedTagsToVariants {
21+
public:
22+
template <class StructType>
23+
static auto process(auto&& _named_tuple) {
24+
return _named_tuple;
25+
}
26+
};
27+
1728
} // namespace rfl
1829

1930
#endif

include/rfl/Processors.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ struct Processors;
1919
template <>
2020
struct Processors<> {
2121
static constexpr bool add_tags_to_variants_ = false;
22+
static constexpr bool add_namespaced_tags_to_variants_ = false;
2223
static constexpr bool allow_raw_ptrs_ = false;
2324
static constexpr bool all_required_ = false;
2425
static constexpr bool default_if_missing_ = false;
@@ -38,6 +39,10 @@ struct Processors<Head, Tail...> {
3839
std::disjunction_v<internal::is_add_tags_to_variants<Head>,
3940
internal::is_add_tags_to_variants<Tail>...>;
4041

42+
static constexpr bool add_namespaced_tags_to_variants_ =
43+
std::disjunction_v<internal::is_add_namespaced_tags_to_variants<Head>,
44+
internal::is_add_namespaced_tags_to_variants<Tail>...>;
45+
4146
static constexpr bool allow_raw_ptrs_ =
4247
std::disjunction_v<internal::is_allow_raw_ptrs<Head>,
4348
internal::is_allow_raw_ptrs<Tail>...>;
@@ -64,6 +69,9 @@ struct Processors<Head, Tail...> {
6469

6570
template <class T, class NamedTupleType>
6671
static auto process(NamedTupleType&& _named_tuple) {
72+
static_assert(!add_tags_to_variants_ || !add_namespaced_tags_to_variants_,
73+
"You cannot add both rfl::AddTagsToVariants and "
74+
"rfl::AddNamespacedTagsToVariants.");
6775
return Processors<Tail...>::template process<T>(
6876
Head::template process<T>(std::move(_named_tuple)));
6977
}

include/rfl/internal/is_add_tags_to_variants_v.hpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@ template <class T>
2222
constexpr bool is_add_tags_to_variants_v = is_add_tags_to_variants<
2323
std::remove_cvref_t<std::remove_pointer_t<T>>>::value;
2424

25+
template <class T>
26+
class is_add_namespaced_tags_to_variants;
27+
28+
template <class T>
29+
class is_add_namespaced_tags_to_variants : public std::false_type {};
30+
31+
template <>
32+
class is_add_namespaced_tags_to_variants<AddNamespacedTagsToVariants> : public std::true_type {};
33+
34+
template <class T>
35+
constexpr bool is_add_namespaced_tags_to_variants_v = is_add_namespaced_tags_to_variants<
36+
std::remove_cvref_t<std::remove_pointer_t<T>>>::value;
37+
38+
2539
} // namespace rfl::internal
2640

2741
#endif

include/rfl/parsing/Parser_rfl_variant.hpp

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@ class Parser<R, W, rfl::Variant<AlternativeTypes...>, ProcessorsType> {
5353
return _r.template read_union<Variant<AlternativeTypes...>, V>(_u);
5454
});
5555

56-
} else if constexpr (ProcessorsType::add_tags_to_variants_) {
56+
} else if constexpr (ProcessorsType::add_tags_to_variants_ ||
57+
ProcessorsType::add_namespaced_tags_to_variants_) {
58+
constexpr bool remove_namespaces = ProcessorsType::add_tags_to_variants_;
5759
using FieldVariantType =
58-
rfl::Variant<VariantAlternativeWrapper<AlternativeTypes>...>;
60+
rfl::Variant<VariantAlternativeWrapper<AlternativeTypes, remove_namespaces>...>;
5961
const auto from_field_variant =
6062
[](auto&& _field) -> rfl::Variant<AlternativeTypes...> {
6163
return std::move(_field.value());
@@ -119,12 +121,14 @@ class Parser<R, W, rfl::Variant<AlternativeTypes...>, ProcessorsType> {
119121
},
120122
_variant);
121123

122-
} else if constexpr (ProcessorsType::add_tags_to_variants_) {
124+
} else if constexpr (ProcessorsType::add_tags_to_variants_ ||
125+
ProcessorsType::add_namespaced_tags_to_variants_) {
126+
constexpr bool remove_namespaces = ProcessorsType::add_tags_to_variants_;
123127
using FieldVariantType =
124-
rfl::Variant<VariantAlternativeWrapper<const AlternativeTypes*>...>;
128+
rfl::Variant<VariantAlternativeWrapper<const AlternativeTypes*, remove_namespaces>...>;
125129
const auto to_field_variant =
126130
[]<class T>(const T& _t) -> FieldVariantType {
127-
return VariantAlternativeWrapper<const T*>(&_t);
131+
return VariantAlternativeWrapper<const T*, remove_namespaces>(&_t);
128132
};
129133
Parser<R, W, FieldVariantType, ProcessorsType>::write(
130134
_w, _variant.visit(to_field_variant), _parent);
@@ -144,9 +148,11 @@ class Parser<R, W, rfl::Variant<AlternativeTypes...>, ProcessorsType> {
144148
return FieldVariantParser<R, W, ProcessorsType,
145149
AlternativeTypes...>::to_schema(_definitions);
146150

147-
} else if constexpr (ProcessorsType::add_tags_to_variants_) {
151+
} else if constexpr (ProcessorsType::add_tags_to_variants_ ||
152+
ProcessorsType::add_namespaced_tags_to_variants_) {
153+
constexpr bool remove_namespaces = ProcessorsType::add_tags_to_variants_;
148154
using FieldVariantType =
149-
rfl::Variant<VariantAlternativeWrapper<AlternativeTypes>...>;
155+
rfl::Variant<VariantAlternativeWrapper<AlternativeTypes, remove_namespaces>...>;
150156
return Parser<R, W, FieldVariantType, ProcessorsType>::to_schema(
151157
_definitions);
152158

include/rfl/parsing/Parser_variant.hpp

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,11 @@ class Parser<R, W, std::variant<AlternativeTypes...>, ProcessorsType> {
7070
return _r.template read_union<std::variant<AlternativeTypes...>, V>(_u);
7171
});
7272

73-
} else if constexpr (ProcessorsType::add_tags_to_variants_) {
73+
} else if constexpr (ProcessorsType::add_tags_to_variants_ ||
74+
ProcessorsType::add_namespaced_tags_to_variants_) {
75+
constexpr bool remove_namespaces = ProcessorsType::add_tags_to_variants_;
7476
using FieldVariantType =
75-
rfl::Variant<VariantAlternativeWrapper<AlternativeTypes>...>;
77+
rfl::Variant<VariantAlternativeWrapper<AlternativeTypes, remove_namespaces>...>;
7678
const auto from_field_variant =
7779
[](auto&& _field) -> std::variant<AlternativeTypes...> {
7880
return std::move(_field.value());
@@ -148,12 +150,14 @@ class Parser<R, W, std::variant<AlternativeTypes...>, ProcessorsType> {
148150
},
149151
_variant);
150152

151-
} else if constexpr (ProcessorsType::add_tags_to_variants_) {
153+
} else if constexpr (ProcessorsType::add_tags_to_variants_ ||
154+
ProcessorsType::add_namespaced_tags_to_variants_) {
155+
constexpr bool remove_namespaces = ProcessorsType::add_tags_to_variants_;
152156
using FieldVariantType =
153-
rfl::Variant<VariantAlternativeWrapper<const AlternativeTypes*>...>;
157+
rfl::Variant<VariantAlternativeWrapper<const AlternativeTypes*, remove_namespaces>...>;
154158
const auto to_field_variant =
155159
[]<class T>(const T& _t) -> FieldVariantType {
156-
return VariantAlternativeWrapper<const T*>(&_t);
160+
return VariantAlternativeWrapper<const T*, remove_namespaces>(&_t);
157161
};
158162
Parser<R, W, FieldVariantType, ProcessorsType>::write(
159163
_w, std::visit(to_field_variant, _variant), _parent);
@@ -173,9 +177,11 @@ class Parser<R, W, std::variant<AlternativeTypes...>, ProcessorsType> {
173177
return FieldVariantParser<R, W, ProcessorsType,
174178
AlternativeTypes...>::to_schema(_definitions);
175179

176-
} else if constexpr (ProcessorsType::add_tags_to_variants_) {
180+
} else if constexpr (ProcessorsType::add_tags_to_variants_ ||
181+
ProcessorsType::add_namespaced_tags_to_variants_) {
182+
constexpr bool remove_namespaces = ProcessorsType::add_tags_to_variants_;
177183
using FieldVariantType =
178-
rfl::Variant<VariantAlternativeWrapper<AlternativeTypes>...>;
184+
rfl::Variant<VariantAlternativeWrapper<AlternativeTypes, remove_namespaces>...>;
179185
return Parser<R, W, FieldVariantType, ProcessorsType>::to_schema(
180186
_definitions);
181187

include/rfl/parsing/VariantAlternativeWrapper.hpp

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,28 @@ struct GetName<Literal<_name>> {
2222
constexpr static internal::StringLiteral name_ = _name;
2323
};
2424

25-
template <class T>
25+
template <class T, bool _remove_namespaces = true>
2626
consteval auto make_tag() {
2727
if constexpr (internal::has_tag_v<T>) {
2828
return typename T::Tag();
29-
} else {
29+
} else if constexpr (std::is_same_v<std::remove_cvref_t<T>, std::string>) {
30+
return Literal<"std::string">();
31+
} else if constexpr (_remove_namespaces) {
3032
return Literal<
3133
internal::remove_namespaces<internal::get_type_name<T>()>()>();
34+
} else {
35+
return Literal<internal::get_type_name<T>()>();
3236
}
3337
}
3438

35-
template <class T>
39+
template <class T, bool _remove_namespaces = true>
3640
using tag_t = std::invoke_result_t<
37-
decltype(make_tag<std::remove_cvref_t<std::remove_pointer_t<T>>>)>;
41+
decltype(make_tag<std::remove_cvref_t<std::remove_pointer_t<T>>, _remove_namespaces>)>;
3842

3943
} // namespace vaw
4044

41-
template <class T>
42-
using VariantAlternativeWrapper = Field<vaw::GetName<vaw::tag_t<T>>::name_, T>;
45+
template <class T, bool _remove_namespaces = true>
46+
using VariantAlternativeWrapper = Field<vaw::GetName<vaw::tag_t<T, _remove_namespaces>>::name_, T>;
4347

4448
} // namespace rfl::parsing
4549

include/rfl/parsing/tabular/ArrowReader.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ template <class VecType, SerializationType _s, class... Ps>
2727
class ArrowReader {
2828
static_assert(!Processors<Ps...>::add_tags_to_variants_,
2929
"rfl::AddTagsToVariants cannot be used for tabular data.");
30+
static_assert(!Processors<Ps...>::add_namespaced_tags_to_variants_,
31+
"rfl::AddNamespacedTagsToVariants cannot be used for tabular data.");
3032
static_assert(!Processors<Ps...>::all_required_,
3133
"rfl::NoOptionals cannot be used for tabular data.");
3234
static_assert(!Processors<Ps...>::default_if_missing_,

include/rfl/parsing/tabular/ArrowWriter.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ template <class VecType, SerializationType _s, class... Ps>
2424
class ArrowWriter {
2525
static_assert(!Processors<Ps...>::add_tags_to_variants_,
2626
"rfl::AddTagsToVariants cannot be used for tabular data.");
27+
static_assert(!Processors<Ps...>::add_namespaced_tags_to_variants_,
28+
"rfl::AddNamespacedTagsToVariants cannot be used for tabular data.");
2729
static_assert(!Processors<Ps...>::all_required_,
2830
"rfl::NoOptionals cannot be used for tabular data.");
2931
static_assert(!Processors<Ps...>::default_if_missing_,

0 commit comments

Comments
 (0)