-
Notifications
You must be signed in to change notification settings - Fork 179
Added rfl::DefaultVal, resolves #534 #553
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
98d8cad
Added rfl::DefaultVal
liuzicheng1987 b15b874
Merge branch 'main' into f/default
liuzicheng1987 c7e8b6e
Added missing headers
liuzicheng1987 525df8b
Update DefaultVal.hpp
liuzicheng1987 e1ef247
Update DefaultVal.hpp
liuzicheng1987 a247bc3
Update DefaultVal.hpp
liuzicheng1987 14bc974
Fixed some minor errors
liuzicheng1987 f3721fa
Added documentation
liuzicheng1987 95ce663
Added missing header
liuzicheng1987 23ce0b1
Added more headers
liuzicheng1987 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| # Default values (rfl::DefaultVal) | ||
|
|
||
| The `rfl::DefaultVal<T>` wrapper allows a struct field to have a predefined default value when serializing and deserializing. When a field is declared as `rfl::DefaultVal<T>`, the library will accept input that omits that field and will populate it with the provided default (or with a default-constructed T when no explicit default is given). | ||
|
|
||
| ## Declaration and initialization | ||
|
|
||
| You can declare a default-valued field like this: | ||
|
|
||
| ```cpp | ||
| struct Person { | ||
| std::string first_name; // required | ||
| rfl::DefaultVal<std::string> last_name = "Simpson"; // has explicit default | ||
| rfl::DefaultVal<std::string> town; // default-constructed (empty string) | ||
| }; | ||
| ``` | ||
|
|
||
| DefaultVal behaves like a thin wrapper around the underlying type. You can construct and assign it from the underlying type, from other DefaultVal instances (if convertible), or assign the special token `rfl::Default` to reset it to the default-constructed value (if the type is default-constructible): | ||
|
|
||
| ```cpp | ||
| Person p; | ||
| p.last_name = "Smith"; // assign underlying value | ||
| p.town = rfl::Default{}; // reset to default (empty string) | ||
| std::string s = p.last_name.value(); | ||
| ``` | ||
|
|
||
| API convenience: | ||
|
|
||
| - .get(), .value(), operator()() — access the underlying value (const and non-const overloads). | ||
| - set(...) — assign underlying value. | ||
|
|
||
| ## JSON behaviour | ||
|
|
||
| When writing JSON, fields that are DefaultVal are written like normal fields using their current underlying value. When reading JSON, omitted DefaultVal fields are filled with the default value (the value assigned in the declaration, or the type's default-constructed value). | ||
|
|
||
| Example (object fields): | ||
|
|
||
| ```cpp | ||
| // Person from above | ||
| const auto homer = rfl::json::read<Person>(R"({"first_name":"Homer"})").value(); | ||
| // homer.last_name == "Simpson" (declared default) | ||
| // homer.town == "" (default-constructed) | ||
| ``` | ||
|
|
||
| Example (no field names / positional arrays): | ||
|
|
||
| DefaultVal also works when using rfl::NoFieldNames (positional JSON arrays). Omitted positions that correspond to DefaultVal fields get their default values: | ||
|
|
||
| ```cpp | ||
| const auto homer = rfl::json::read<Person, rfl::NoFieldNames>(R"(["Homer"])" ).value(); | ||
| // homer.first_name == "Homer" | ||
| // homer.last_name == "Simpson" | ||
| // homer.town == "" | ||
| ``` | ||
|
|
||
| ## When to use | ||
|
|
||
| Use rfl::DefaultVal when you want a field to be optional at the input side but still available as a value on the resulting object (no std::optional or pointer indirection). It is particularly useful for fields with sensible defaults (for example, a common last name, a default configuration value, or empty containers/strings). | ||
|
|
||
| ## Notes | ||
|
|
||
| - The underlying type must be default-constructible to allow resetting via `rfl::Default` or when no explicit default is supplied. | ||
| - DefaultVal preserves normal read/write semantics; other fields that are not DefaultVal remain required unless expressed as optionals or handled by processors (e.g., rfl::DefaultIfMissing). | ||
|
|
||
| For more advanced control over when fields are considered missing and how defaults are applied, see the processors documentation (e.g., `rfl::DefaultIfMissing`). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| #ifndef RFL_DEFAULTVAL_HPP_ | ||
| #define RFL_DEFAULTVAL_HPP_ | ||
|
|
||
| #include <type_traits> | ||
| #include <utility> | ||
|
|
||
| #include "default.hpp" | ||
|
|
||
| namespace rfl { | ||
|
|
||
| template <class T> | ||
| struct DefaultVal { | ||
| public: | ||
| using Type = std::remove_cvref_t<T>; | ||
|
|
||
| DefaultVal() : value_(Type()) {} | ||
|
|
||
| DefaultVal(const Type& _value) : value_(_value) {} | ||
|
|
||
| DefaultVal(Type&& _value) noexcept : value_(std::move(_value)) {} | ||
|
|
||
| DefaultVal(DefaultVal&& _field) noexcept = default; | ||
|
|
||
| DefaultVal(const DefaultVal& _field) = default; | ||
|
|
||
| template <class U> | ||
| DefaultVal(const DefaultVal<U>& _field) : value_(_field.get()) {} | ||
|
|
||
| template <class U> | ||
| DefaultVal(DefaultVal<U>&& _field) noexcept( | ||
| noexcept(Type(std::move(_field.value())))) | ||
| : value_(std::move(_field.value())) {} | ||
|
|
||
| template <class U> | ||
| requires(std::is_convertible_v<U, Type>) | ||
| DefaultVal(const U& _value) : value_(_value) {} | ||
|
|
||
| template <class U> | ||
| requires(std::is_convertible_v<U, Type>) | ||
| DefaultVal(U&& _value) noexcept : value_(std::forward<U>(_value)) {} | ||
|
|
||
| template <class U> | ||
| requires(std::is_convertible_v<U, Type>) | ||
| DefaultVal(const DefaultVal<U>& _field) : value_(_field.value()) {} | ||
|
|
||
| /// Assigns the underlying object to its default value. | ||
| template <class U = Type> | ||
| requires(std::is_default_constructible_v<U>) | ||
| DefaultVal(const Default&) : value_(Type()) {} | ||
|
|
||
| ~DefaultVal() = default; | ||
|
|
||
| /// Returns the underlying object. | ||
| const Type& get() const { return value_; } | ||
|
|
||
| /// Returns the underlying object. | ||
| Type& operator()() { return value_; } | ||
|
|
||
| /// Returns the underlying object. | ||
| const Type& operator()() const { return value_; } | ||
|
|
||
| /// Assigns the underlying object. | ||
| auto& operator=(const Type& _value) { | ||
| value_ = _value; | ||
| return *this; | ||
| } | ||
|
|
||
| /// Assigns the underlying object. | ||
| auto& operator=(Type&& _value) noexcept { | ||
| value_ = std::move(_value); | ||
| return *this; | ||
| } | ||
|
|
||
| /// Assigns the underlying object. | ||
| template <class U, typename std::enable_if<std::is_convertible_v<U, Type>, | ||
| bool>::type = true> | ||
| auto& operator=(const U& _value) { | ||
| value_ = _value; | ||
| return *this; | ||
| } | ||
|
|
||
| /// Assigns the underlying object to its default value. | ||
| template <class U = Type, | ||
| typename std::enable_if<std::is_default_constructible_v<U>, | ||
| bool>::type = true> | ||
| auto& operator=(const Default&) { | ||
| value_ = Type(); | ||
| return *this; | ||
| } | ||
|
|
||
| /// Assigns the underlying object. | ||
| DefaultVal& operator=(const DefaultVal& _field) = default; | ||
|
|
||
| /// Assigns the underlying object. | ||
| DefaultVal& operator=(DefaultVal&& _field) = default; | ||
|
|
||
| /// Assigns the underlying object. | ||
| template <class U> | ||
| auto& operator=(const DefaultVal<U>& _field) { | ||
| value_ = _field.get(); | ||
| return *this; | ||
| } | ||
|
|
||
| /// Assigns the underlying object. | ||
| template <class U> | ||
| auto& operator=(DefaultVal<U>&& _field) { | ||
| value_ = std::forward<U>(_field.value_); | ||
| return *this; | ||
|
liuzicheng1987 marked this conversation as resolved.
|
||
| } | ||
|
|
||
| /// Assigns the underlying object. | ||
| void set(const Type& _value) { value_ = _value; } | ||
|
|
||
| /// Assigns the underlying object. | ||
| void set(Type&& _value) { value_ = std::move(_value); } | ||
|
|
||
| /// Returns the underlying object. | ||
| Type& value() { return value_; } | ||
|
|
||
| /// Returns the underlying object. | ||
| const Type& value() const { return value_; } | ||
|
|
||
| /// The underlying value. | ||
| Type value_; | ||
| }; | ||
|
|
||
| } // namespace rfl | ||
|
|
||
| #endif | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| #ifndef RFL_HASDEFAULTVALV_HPP_ | ||
| #define RFL_HASDEFAULTVALV_HPP_ | ||
| #include <type_traits> | ||
|
|
||
| #include "../NamedTuple.hpp" | ||
| #include "../named_tuple_t.hpp" | ||
| #include "is_default_val_v.hpp" | ||
|
|
||
| namespace rfl::internal { | ||
|
|
||
| template <class T> | ||
| struct HasDefaultVal; | ||
|
|
||
| template <class... Fields> | ||
| struct HasDefaultVal<NamedTuple<Fields...>> { | ||
| static constexpr bool value = | ||
| (false || ... || | ||
| is_default_val_v< | ||
| std::remove_cvref_t<std::remove_pointer_t<typename Fields::Type>>>); | ||
| }; | ||
|
|
||
| template <class T> | ||
| constexpr bool has_default_val_v = HasDefaultVal<named_tuple_t<T>>::value; | ||
|
|
||
| } // namespace rfl::internal | ||
|
|
||
| #endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,30 +1,21 @@ | ||
| #ifndef RFL_HASTAGV_HPP_ | ||
| #define RFL_HASTAGV_HPP_ | ||
|
|
||
| #include <cstdint> | ||
| #include <concepts> | ||
|
|
||
| namespace rfl { | ||
| namespace internal { | ||
| namespace rfl::internal { | ||
|
|
||
| template <class Wrapper> | ||
| class HasTag { | ||
| private: | ||
| template <class U> | ||
| static std::int64_t foo(...); | ||
|
|
||
| template <class U> | ||
| static std::int32_t foo(typename U::Tag*); | ||
|
|
||
| public: | ||
| static constexpr bool value = | ||
| sizeof(foo<Wrapper>(nullptr)) == sizeof(std::int32_t); | ||
| }; | ||
| template <class T> | ||
| struct TagWrapper {}; | ||
|
|
||
| /// Used for tagged unions - determines whether a struct as a Tag. | ||
| template <typename Wrapper> | ||
| constexpr bool has_tag_v = HasTag<Wrapper>::value; | ||
| template <typename T> | ||
| constexpr bool has_tag_v = requires() { | ||
| { | ||
| TagWrapper<typename T::Tag>{} | ||
| } -> std::same_as<TagWrapper<typename T::Tag>>; | ||
| }; | ||
|
|
||
| } // namespace internal | ||
| } // namespace rfl | ||
| } // namespace rfl::internal | ||
|
|
||
| #endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| #ifndef RFL_INTERNAL_ISDEFAULTVAL_HPP_ | ||
| #define RFL_INTERNAL_ISDEFAULTVAL_HPP_ | ||
|
|
||
| #include <type_traits> | ||
|
|
||
| #include "../DefaultVal.hpp" | ||
|
|
||
| namespace rfl::internal { | ||
|
|
||
| template <class T> | ||
| class is_default_val; | ||
|
|
||
| template <class T> | ||
| class is_default_val : public std::false_type {}; | ||
|
|
||
| template <class T> | ||
| class is_default_val<DefaultVal<T>> : public std::true_type {}; | ||
|
|
||
| template <class T> | ||
| constexpr bool is_default_val_v = | ||
| is_default_val<std::remove_cvref_t<std::remove_pointer_t<T>>>::value; | ||
|
|
||
| } // namespace rfl::internal | ||
|
|
||
| #endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This unconstrained converting copy constructor is redundant with the constrained version on lines 42-44. Having both can lead to ambiguity during template resolution. It would be safer to remove this one and rely solely on the constrained version.