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
84 changes: 84 additions & 0 deletions docs/concepts/processors.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ reflect-cpp currently supports the following processors:

- `rfl::AddStructName`
- `rfl::AddTagsToVariants`
- `rfl::AddNamespacedTagsToVariants`
- `rfl::AllowRawPtrs`
- `rfl::DefaultIfMissing`
- `rfl::NoExtraFields`
Expand Down Expand Up @@ -117,6 +118,89 @@ struct key_pressed_t {
Note that there are other ways to address problems like this, for instance `rfl::TaggedUnion`.
Please refer to the relevant sections of the documentation.

### `rfl::AddNamespacedTagsToVariants`

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:

1. Structs with the same name in different namespaces
2. Structs with `rfl::Generic` fields that would otherwise create naming conflicts

#### Use-case: Structs with the same name in different namespaces

Consider this example where `rfl::AddTagsToVariants` would fail:

```cpp
namespace Result {
struct Message {
std::string result;
};
}

namespace Error {
struct Message {
std::string error;
int error_id;
};
}

using Messages = std::variant<Result::Message, Error::Message>;

const auto msgs = std::vector<Messages>{
Result::Message{.result = "success"},
Error::Message{.error = "failure", .error_id = 404}
};

// This would cause problems with rfl::AddTagsToVariants because both
// structs have the same name "Message"

// But this works perfectly:
const auto json_string = rfl::json::write<rfl::AddNamespacedTagsToVariants>(msgs);
const auto msgs2 = rfl::json::read<std::vector<Messages>, rfl::AddNamespacedTagsToVariants>(json_string);
```

The resulting JSON includes the full namespace path:

```json
[
{"Result::Message": {"result": "success"}},
{"Error::Message": {"error": "failure", "error_id": 404}}
]
```

#### Use-case: Structs with `rfl::Generic` fields that would otherwise create naming conflicts

Another use case is with `rfl::Generic` fields, where multiple structs contain generic fields that would otherwise create naming conflicts:

```cpp
struct APIResult {
rfl::Generic result; // Could be string, number, object, etc.
};

struct APIError {
rfl::Generic error; // Could be string, number, object, etc.
};

using APIResponse = std::variant<APIResult, APIError>;

const auto response = APIResult{.result = std::string("200")};

// Without namespaces, both Generic fields would have the same tag name.
// With namespaces, they get unique identifiers:
const auto json_string = rfl::json::write<rfl::AddNamespacedTagsToVariants>(response);
```

This generates:

```json
{"APIResult": {"result": {"std::string": "200"}}}
```

#### When to use `rfl::AddNamespacedTagsToVariants` vs `rfl::AddTagsToVariants`

- Use `rfl::AddTagsToVariants` when struct names are unique and you want shorter, cleaner tags
- Use `rfl::AddNamespacedTagsToVariants` when you have naming conflicts or need to distinguish between types in different namespaces
- Custom tags (using `Tag = rfl::Literal<"custom_name">`) work with both processors and are not affected by the namespace handling

### `rfl::AllowRawPtrs`

By default, reflect-cpp does not allow *reading into* raw pointers, `std::string_view` or `std::span`.
Expand Down
38 changes: 38 additions & 0 deletions docs/variants_and_tagged_unions.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,44 @@ const auto r2 = rfl::json::read<Shapes, rfl::AddTagsToVariants>(json_string);

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

## Automatic tags with full namespaces

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:

```cpp
namespace Result {
struct Message {
std::string result;
};
}

namespace Error {
struct Message {
std::string error;
int error_id;
};
}

using Messages = std::variant<Result::Message, Error::Message>;

const Messages msg = Error::Message{.error = "Something went wrong", .error_id = 404};

const auto json_string = rfl::json::write<rfl::AddNamespacedTagsToVariants>(msg);
```

This generates JSON with fully qualified type names:

```json
{"Error::Message":{"error":"Something went wrong","error_id":404}}
```

The key differences between `rfl::AddTagsToVariants` and `rfl::AddNamespacedTagsToVariants`:

- `rfl::AddTagsToVariants` uses just the struct name (e.g., `"Message"`)
- `rfl::AddNamespacedTagsToVariants` uses the full namespaced name (e.g., `"Error::Message"`)
- Both respect custom tags defined with `using Tag = rfl::Literal<"custom_name">`
- 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

## `rfl::TaggedUnion` (internally tagged)

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`.
Expand Down
11 changes: 11 additions & 0 deletions include/rfl/AddTagsToVariants.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ struct AddTagsToVariants {
}
};

/// This is a "fake" processor - it doesn't do much in itself, but its
/// inclusion instructs the parsers to automatically add tags to the variants
/// they might encounter.
struct AddNamespacedTagsToVariants {
public:
template <class StructType>
static auto process(auto&& _named_tuple) {
return _named_tuple;
}
};

} // namespace rfl

#endif
8 changes: 8 additions & 0 deletions include/rfl/Processors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ struct Processors;
template <>
struct Processors<> {
static constexpr bool add_tags_to_variants_ = false;
static constexpr bool add_namespaced_tags_to_variants_ = false;
static constexpr bool allow_raw_ptrs_ = false;
static constexpr bool all_required_ = false;
static constexpr bool default_if_missing_ = false;
Expand All @@ -38,6 +39,10 @@ struct Processors<Head, Tail...> {
std::disjunction_v<internal::is_add_tags_to_variants<Head>,
internal::is_add_tags_to_variants<Tail>...>;

static constexpr bool add_namespaced_tags_to_variants_ =
Comment thread
bryceschober marked this conversation as resolved.
std::disjunction_v<internal::is_add_namespaced_tags_to_variants<Head>,
internal::is_add_namespaced_tags_to_variants<Tail>...>;

static constexpr bool allow_raw_ptrs_ =
std::disjunction_v<internal::is_allow_raw_ptrs<Head>,
internal::is_allow_raw_ptrs<Tail>...>;
Expand All @@ -64,6 +69,9 @@ struct Processors<Head, Tail...> {

template <class T, class NamedTupleType>
static auto process(NamedTupleType&& _named_tuple) {
static_assert(!add_tags_to_variants_ || !add_namespaced_tags_to_variants_,
"You cannot add both rfl::AddTagsToVariants and "
"rfl::AddNamespacedTagsToVariants.");
return Processors<Tail...>::template process<T>(
Head::template process<T>(std::move(_named_tuple)));
}
Expand Down
14 changes: 14 additions & 0 deletions include/rfl/internal/is_add_tags_to_variants_v.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@ template <class T>
constexpr bool is_add_tags_to_variants_v = is_add_tags_to_variants<
std::remove_cvref_t<std::remove_pointer_t<T>>>::value;

template <class T>
class is_add_namespaced_tags_to_variants;

template <class T>
class is_add_namespaced_tags_to_variants : public std::false_type {};

template <>
class is_add_namespaced_tags_to_variants<AddNamespacedTagsToVariants> : public std::true_type {};

template <class T>
constexpr bool is_add_namespaced_tags_to_variants_v = is_add_namespaced_tags_to_variants<
std::remove_cvref_t<std::remove_pointer_t<T>>>::value;


} // namespace rfl::internal

#endif
20 changes: 13 additions & 7 deletions include/rfl/parsing/Parser_rfl_variant.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ class Parser<R, W, rfl::Variant<AlternativeTypes...>, ProcessorsType> {
return _r.template read_union<Variant<AlternativeTypes...>, V>(_u);
});

} else if constexpr (ProcessorsType::add_tags_to_variants_) {
} else if constexpr (ProcessorsType::add_tags_to_variants_ ||
ProcessorsType::add_namespaced_tags_to_variants_) {
constexpr bool remove_namespaces = ProcessorsType::add_tags_to_variants_;
using FieldVariantType =
rfl::Variant<VariantAlternativeWrapper<AlternativeTypes>...>;
rfl::Variant<VariantAlternativeWrapper<AlternativeTypes, remove_namespaces>...>;
const auto from_field_variant =
[](auto&& _field) -> rfl::Variant<AlternativeTypes...> {
return std::move(_field.value());
Expand Down Expand Up @@ -119,12 +121,14 @@ class Parser<R, W, rfl::Variant<AlternativeTypes...>, ProcessorsType> {
},
_variant);

} else if constexpr (ProcessorsType::add_tags_to_variants_) {
} else if constexpr (ProcessorsType::add_tags_to_variants_ ||
ProcessorsType::add_namespaced_tags_to_variants_) {
constexpr bool remove_namespaces = ProcessorsType::add_tags_to_variants_;
using FieldVariantType =
rfl::Variant<VariantAlternativeWrapper<const AlternativeTypes*>...>;
rfl::Variant<VariantAlternativeWrapper<const AlternativeTypes*, remove_namespaces>...>;
const auto to_field_variant =
[]<class T>(const T& _t) -> FieldVariantType {
return VariantAlternativeWrapper<const T*>(&_t);
return VariantAlternativeWrapper<const T*, remove_namespaces>(&_t);
};
Parser<R, W, FieldVariantType, ProcessorsType>::write(
_w, _variant.visit(to_field_variant), _parent);
Expand All @@ -144,9 +148,11 @@ class Parser<R, W, rfl::Variant<AlternativeTypes...>, ProcessorsType> {
return FieldVariantParser<R, W, ProcessorsType,
AlternativeTypes...>::to_schema(_definitions);

} else if constexpr (ProcessorsType::add_tags_to_variants_) {
} else if constexpr (ProcessorsType::add_tags_to_variants_ ||
ProcessorsType::add_namespaced_tags_to_variants_) {
constexpr bool remove_namespaces = ProcessorsType::add_tags_to_variants_;
using FieldVariantType =
rfl::Variant<VariantAlternativeWrapper<AlternativeTypes>...>;
rfl::Variant<VariantAlternativeWrapper<AlternativeTypes, remove_namespaces>...>;
return Parser<R, W, FieldVariantType, ProcessorsType>::to_schema(
_definitions);

Expand Down
20 changes: 13 additions & 7 deletions include/rfl/parsing/Parser_variant.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,11 @@ class Parser<R, W, std::variant<AlternativeTypes...>, ProcessorsType> {
return _r.template read_union<std::variant<AlternativeTypes...>, V>(_u);
});

} else if constexpr (ProcessorsType::add_tags_to_variants_) {
} else if constexpr (ProcessorsType::add_tags_to_variants_ ||
ProcessorsType::add_namespaced_tags_to_variants_) {
constexpr bool remove_namespaces = ProcessorsType::add_tags_to_variants_;
using FieldVariantType =
rfl::Variant<VariantAlternativeWrapper<AlternativeTypes>...>;
rfl::Variant<VariantAlternativeWrapper<AlternativeTypes, remove_namespaces>...>;
const auto from_field_variant =
[](auto&& _field) -> std::variant<AlternativeTypes...> {
return std::move(_field.value());
Expand Down Expand Up @@ -148,12 +150,14 @@ class Parser<R, W, std::variant<AlternativeTypes...>, ProcessorsType> {
},
_variant);

} else if constexpr (ProcessorsType::add_tags_to_variants_) {
} else if constexpr (ProcessorsType::add_tags_to_variants_ ||
ProcessorsType::add_namespaced_tags_to_variants_) {
constexpr bool remove_namespaces = ProcessorsType::add_tags_to_variants_;
using FieldVariantType =
rfl::Variant<VariantAlternativeWrapper<const AlternativeTypes*>...>;
rfl::Variant<VariantAlternativeWrapper<const AlternativeTypes*, remove_namespaces>...>;
const auto to_field_variant =
[]<class T>(const T& _t) -> FieldVariantType {
return VariantAlternativeWrapper<const T*>(&_t);
return VariantAlternativeWrapper<const T*, remove_namespaces>(&_t);
};
Parser<R, W, FieldVariantType, ProcessorsType>::write(
_w, std::visit(to_field_variant, _variant), _parent);
Expand All @@ -173,9 +177,11 @@ class Parser<R, W, std::variant<AlternativeTypes...>, ProcessorsType> {
return FieldVariantParser<R, W, ProcessorsType,
AlternativeTypes...>::to_schema(_definitions);

} else if constexpr (ProcessorsType::add_tags_to_variants_) {
} else if constexpr (ProcessorsType::add_tags_to_variants_ ||
ProcessorsType::add_namespaced_tags_to_variants_) {
constexpr bool remove_namespaces = ProcessorsType::add_tags_to_variants_;
using FieldVariantType =
rfl::Variant<VariantAlternativeWrapper<AlternativeTypes>...>;
rfl::Variant<VariantAlternativeWrapper<AlternativeTypes, remove_namespaces>...>;
return Parser<R, W, FieldVariantType, ProcessorsType>::to_schema(
_definitions);

Expand Down
16 changes: 10 additions & 6 deletions include/rfl/parsing/VariantAlternativeWrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,28 @@ struct GetName<Literal<_name>> {
constexpr static internal::StringLiteral name_ = _name;
};

template <class T>
template <class T, bool _remove_namespaces = true>
consteval auto make_tag() {
if constexpr (internal::has_tag_v<T>) {
return typename T::Tag();
} else {
} else if constexpr (std::is_same_v<std::remove_cvref_t<T>, std::string>) {
return Literal<"std::string">();
} else if constexpr (_remove_namespaces) {
return Literal<
internal::remove_namespaces<internal::get_type_name<T>()>()>();
} else {
return Literal<internal::get_type_name<T>()>();
}
}

template <class T>
template <class T, bool _remove_namespaces = true>
using tag_t = std::invoke_result_t<
decltype(make_tag<std::remove_cvref_t<std::remove_pointer_t<T>>>)>;
decltype(make_tag<std::remove_cvref_t<std::remove_pointer_t<T>>, _remove_namespaces>)>;

} // namespace vaw

template <class T>
using VariantAlternativeWrapper = Field<vaw::GetName<vaw::tag_t<T>>::name_, T>;
template <class T, bool _remove_namespaces = true>
using VariantAlternativeWrapper = Field<vaw::GetName<vaw::tag_t<T, _remove_namespaces>>::name_, T>;

} // namespace rfl::parsing

Expand Down
2 changes: 2 additions & 0 deletions include/rfl/parsing/tabular/ArrowReader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ template <class VecType, SerializationType _s, class... Ps>
class ArrowReader {
static_assert(!Processors<Ps...>::add_tags_to_variants_,
"rfl::AddTagsToVariants cannot be used for tabular data.");
static_assert(!Processors<Ps...>::add_namespaced_tags_to_variants_,
"rfl::AddNamespacedTagsToVariants cannot be used for tabular data.");
static_assert(!Processors<Ps...>::all_required_,
"rfl::NoOptionals cannot be used for tabular data.");
static_assert(!Processors<Ps...>::default_if_missing_,
Expand Down
2 changes: 2 additions & 0 deletions include/rfl/parsing/tabular/ArrowWriter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ template <class VecType, SerializationType _s, class... Ps>
class ArrowWriter {
static_assert(!Processors<Ps...>::add_tags_to_variants_,
"rfl::AddTagsToVariants cannot be used for tabular data.");
static_assert(!Processors<Ps...>::add_namespaced_tags_to_variants_,
"rfl::AddNamespacedTagsToVariants cannot be used for tabular data.");
static_assert(!Processors<Ps...>::all_required_,
"rfl::NoOptionals cannot be used for tabular data.");
static_assert(!Processors<Ps...>::default_if_missing_,
Expand Down
Loading
Loading