From 98d8cad96bad7653823f9e147d8292943e3a455f Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 30 Nov 2025 18:33:22 +0100 Subject: [PATCH 1/9] Added rfl::DefaultVal --- include/rfl.hpp | 1 + include/rfl/DefaultVal.hpp | 130 ++++++++++++++++++ include/rfl/internal/has_default_val_v.hpp | 27 ++++ include/rfl/internal/has_tag_v.hpp | 31 ++--- include/rfl/internal/is_default_val_v.hpp | 25 ++++ include/rfl/parsing/NamedTupleParser.hpp | 13 +- include/rfl/parsing/Parser.hpp | 1 + include/rfl/parsing/Parser_default.hpp | 4 +- include/rfl/parsing/Parser_default_val.hpp | 41 ++++++ include/rfl/parsing/ViewReaderWithDefault.hpp | 2 + ...ReaderWithDefaultAndStrippedFieldNames.hpp | 7 + tests/json/test_default_val.cpp | 36 +++++ .../json/test_default_val_no_field_names.cpp | 35 +++++ 13 files changed, 330 insertions(+), 23 deletions(-) create mode 100644 include/rfl/DefaultVal.hpp create mode 100644 include/rfl/internal/has_default_val_v.hpp create mode 100644 include/rfl/internal/is_default_val_v.hpp create mode 100644 include/rfl/parsing/Parser_default_val.hpp create mode 100644 tests/json/test_default_val.cpp create mode 100644 tests/json/test_default_val_no_field_names.cpp diff --git a/include/rfl.hpp b/include/rfl.hpp index 4753749c..8aaa0cbc 100644 --- a/include/rfl.hpp +++ b/include/rfl.hpp @@ -17,6 +17,7 @@ #include "rfl/Box.hpp" #include "rfl/Bytestring.hpp" #include "rfl/DefaultIfMissing.hpp" +#include "rfl/DefaultVal.hpp" #include "rfl/Description.hpp" #include "rfl/ExtraFields.hpp" #include "rfl/Field.hpp" diff --git a/include/rfl/DefaultVal.hpp b/include/rfl/DefaultVal.hpp new file mode 100644 index 00000000..0af788f0 --- /dev/null +++ b/include/rfl/DefaultVal.hpp @@ -0,0 +1,130 @@ +#ifndef RFL_DEFAULTVAL_HPP_ +#define RFL_DEFAULTVAL_HPP_ + +#include + +namespace rfl { + +template +struct DefaultVal { + public: + template + static StructType process(NamedTupleType&& _named_tuple) { + return _named_tuple; + } + + using Type = std::remove_cvref_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 + DefaultVal(const DefaultVal& _field) : value_(_field.get()) {} + + template + DefaultVal(DefaultVal&& _field) : value_(_field.get()) {} + + template , + bool>::type = true> + DefaultVal(const U& _value) : value_(_value) {} + + template , + bool>::type = true> + DefaultVal(U&& _value) noexcept : value_(std::forward(_value)) {} + + template , + bool>::type = true> + DefaultVal(const DefaultVal& _field) : value_(_field.value()) {} + + /// Assigns the underlying object to its default value. + template , + bool>::type = true> + 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 , + bool>::type = true> + auto& operator=(const U& _value) { + value_ = _value; + return *this; + } + + /// Assigns the underlying object to its default value. + template , + 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 + auto& operator=(const DefaultVal& _field) { + value_ = _field.get(); + return *this; + } + + /// Assigns the underlying object. + template + auto& operator=(DefaultVal&& _field) { + value_ = std::forward(_field.value_); + return *this; + } + + /// 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 diff --git a/include/rfl/internal/has_default_val_v.hpp b/include/rfl/internal/has_default_val_v.hpp new file mode 100644 index 00000000..6920ae7e --- /dev/null +++ b/include/rfl/internal/has_default_val_v.hpp @@ -0,0 +1,27 @@ +#ifndef RFL_HASDEFAULTVALV_HPP_ +#define RFL_HASDEFAULTVALV_HPP_ +#include + +#include "../NamedTuple.hpp" +#include "../named_tuple_t.hpp" +#include "is_default_val_v.hpp" + +namespace rfl::internal { + +template +struct HasDefaultVal; + +template +struct HasDefaultVal> { + static constexpr bool value = + (false || ... || + is_default_val_v< + std::remove_cvref_t>>); +}; + +template +constexpr bool has_default_val_v = HasDefaultVal>::value; + +} // namespace rfl::internal + +#endif diff --git a/include/rfl/internal/has_tag_v.hpp b/include/rfl/internal/has_tag_v.hpp index 3affc143..299618ff 100644 --- a/include/rfl/internal/has_tag_v.hpp +++ b/include/rfl/internal/has_tag_v.hpp @@ -1,30 +1,21 @@ #ifndef RFL_HASTAGV_HPP_ #define RFL_HASTAGV_HPP_ -#include +#include -namespace rfl { -namespace internal { +namespace rfl::internal { -template -class HasTag { - private: - template - static std::int64_t foo(...); - - template - static std::int32_t foo(typename U::Tag*); - - public: - static constexpr bool value = - sizeof(foo(nullptr)) == sizeof(std::int32_t); -}; +template +struct TagWrapper {}; /// Used for tagged unions - determines whether a struct as a Tag. -template -constexpr bool has_tag_v = HasTag::value; +template +constexpr bool has_tag_v = requires() { + { + TagWrapper{} + } -> std::same_as>; +}; -} // namespace internal -} // namespace rfl +} // namespace rfl::internal #endif diff --git a/include/rfl/internal/is_default_val_v.hpp b/include/rfl/internal/is_default_val_v.hpp new file mode 100644 index 00000000..98977549 --- /dev/null +++ b/include/rfl/internal/is_default_val_v.hpp @@ -0,0 +1,25 @@ +#ifndef RFL_INTERNAL_ISDEFAULTVAL_HPP_ +#define RFL_INTERNAL_ISDEFAULTVAL_HPP_ + +#include + +#include "../DefaultVal.hpp" + +namespace rfl::internal { + +template +class is_default_val; + +template +class is_default_val : public std::false_type {}; + +template +class is_default_val> : public std::true_type {}; + +template +constexpr bool is_default_val_v = + is_default_val>>::value; + +} // namespace rfl::internal + +#endif diff --git a/include/rfl/parsing/NamedTupleParser.hpp b/include/rfl/parsing/NamedTupleParser.hpp index b081d88b..68d71b1f 100644 --- a/include/rfl/parsing/NamedTupleParser.hpp +++ b/include/rfl/parsing/NamedTupleParser.hpp @@ -13,6 +13,7 @@ #include "../internal/is_array.hpp" #include "../internal/is_attribute.hpp" #include "../internal/is_basic_type.hpp" +#include "../internal/is_default_val_v.hpp" #include "../internal/is_extra_fields.hpp" #include "../internal/is_skip.hpp" #include "../internal/no_duplicate_field_names.hpp" @@ -108,7 +109,6 @@ struct NamedTupleParser { auto arr = _r.to_array(_var); if (!arr) [[unlikely]] { auto set = std::array{}; - // return std::make_pair(set, arr.error()); return std::make_pair(set, arr.error()); } return read_object_or_array(_r, *arr, _view); @@ -254,8 +254,10 @@ struct NamedTupleParser { if (!std::get<_i>(_found)) { constexpr bool is_required_field = + !internal::is_default_val_v && !internal::is_extra_fields_v && (_all_required || is_required()); + if constexpr (is_required_field) { constexpr auto current_name = internal::nth_element_t<_i, FieldTypes...>::name(); @@ -263,7 +265,8 @@ struct NamedTupleParser { stream << "Field named '" << std::string(current_name) << "' not found."; _errors->emplace_back(Error(stream.str())); - } else { + + } else if constexpr (!internal::has_default_val_v) { if constexpr (!std::is_const_v) { ::new (rfl::get<_i>(_view)) ValueType(); } else { @@ -338,9 +341,15 @@ struct NamedTupleParser { return err; } } + if constexpr (internal::has_default_val_v && + !ProcessorsType::default_if_missing_) { + handle_missing_fields(reader.found(), *_view, nullptr, &errors, + std::make_integer_sequence()); + } if (errors.size() != 0) { return to_single_error_message(errors); } + return std::nullopt; } }; diff --git a/include/rfl/parsing/Parser.hpp b/include/rfl/parsing/Parser.hpp index c7a1f9fd..0f7ef390 100644 --- a/include/rfl/parsing/Parser.hpp +++ b/include/rfl/parsing/Parser.hpp @@ -7,6 +7,7 @@ #include "Parser_bytestring.hpp" #include "Parser_c_array.hpp" #include "Parser_default.hpp" +#include "Parser_default_val.hpp" #include "Parser_duration.hpp" #include "Parser_filepath.hpp" #include "Parser_map_like.hpp" diff --git a/include/rfl/parsing/Parser_default.hpp b/include/rfl/parsing/Parser_default.hpp index eed67f9a..ced5e8db 100644 --- a/include/rfl/parsing/Parser_default.hpp +++ b/include/rfl/parsing/Parser_default.hpp @@ -8,6 +8,7 @@ #include "../always_false.hpp" #include "../enums.hpp" #include "../from_named_tuple.hpp" +#include "../internal/has_default_val_v.hpp" #include "../internal/has_reflection_method_v.hpp" #include "../internal/has_reflection_type_v.hpp" #include "../internal/has_reflector.hpp" @@ -79,7 +80,8 @@ struct Parser { .and_then(wrap_in_t); } else if constexpr (std::is_class_v && std::is_aggregate_v) { - if constexpr (ProcessorsType::default_if_missing_) { + if constexpr (ProcessorsType::default_if_missing_ || + internal::has_default_val_v) { return read_struct_with_default(_r, _var); } else { return read_struct(_r, _var); diff --git a/include/rfl/parsing/Parser_default_val.hpp b/include/rfl/parsing/Parser_default_val.hpp new file mode 100644 index 00000000..62147ea8 --- /dev/null +++ b/include/rfl/parsing/Parser_default_val.hpp @@ -0,0 +1,41 @@ +#ifndef RFL_PARSING_PARSER_DEFAULTVAL_HPP_ +#define RFL_PARSING_PARSER_DEFAULTVAL_HPP_ + +#include + +#include "../DefaultVal.hpp" + +namespace rfl::parsing { + +template + requires AreReaderAndWriter> +struct Parser, ProcessorsType> { + using InputVarType = typename R::InputVarType; + + using ParentType = Parent; + + static Result> read(const R& _r, + const InputVarType& _var) noexcept { + return Parser, ProcessorsType>::read(_r, _var) + .transform([](auto&& _t) { + return DefaultVal(std::forward(_t)); + }); + } + + template + static void write(const W& _w, const DefaultVal& _d, const P& _parent) { + Parser, ProcessorsType>::write(_w, _d.value(), + _parent); + } + + static schema::Type to_schema( + std::map* _definitions) { + using U = std::remove_cvref_t; + return schema::Type{ + Parser::to_schema(_definitions)}; + } +}; + +} // namespace rfl::parsing + +#endif diff --git a/include/rfl/parsing/ViewReaderWithDefault.hpp b/include/rfl/parsing/ViewReaderWithDefault.hpp index a5a56563..8ee2d79e 100644 --- a/include/rfl/parsing/ViewReaderWithDefault.hpp +++ b/include/rfl/parsing/ViewReaderWithDefault.hpp @@ -31,6 +31,8 @@ class ViewReaderWithDefault { ~ViewReaderWithDefault() = default; + const std::array& found() const { return *found_; } + /// Assigns the parsed version of _var to the field signified by _name, if /// such a field exists in the underlying view. void read(const std::string_view& _name, const InputVarType& _var) const { diff --git a/include/rfl/parsing/ViewReaderWithDefaultAndStrippedFieldNames.hpp b/include/rfl/parsing/ViewReaderWithDefaultAndStrippedFieldNames.hpp index 697060ab..7b12a2d8 100644 --- a/include/rfl/parsing/ViewReaderWithDefaultAndStrippedFieldNames.hpp +++ b/include/rfl/parsing/ViewReaderWithDefaultAndStrippedFieldNames.hpp @@ -27,6 +27,13 @@ class ViewReaderWithDefaultAndStrippedFieldNames { ~ViewReaderWithDefaultAndStrippedFieldNames() = default; + std::array found() const { + std::array f; + std::fill(f.begin(), f.begin() + i_, true); + std::fill(f.begin() + i_, f.end(), false); + return f; + } + /// Assigns the parsed version of _var to the field signified by i_, to be /// used when the field names are stripped. std::optional read(const InputVarType& _var) const { diff --git a/tests/json/test_default_val.cpp b/tests/json/test_default_val.cpp new file mode 100644 index 00000000..1039746e --- /dev/null +++ b/tests/json/test_default_val.cpp @@ -0,0 +1,36 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_default_val { + +struct Person { + std::string first_name; + rfl::DefaultVal last_name = "Simpson"; + rfl::DefaultVal town; +}; + +TEST(json, test_default_val) { + static_assert(rfl::internal::has_default_val_v, + "Should have default val"); + + const auto should_fail = rfl::json::read(R"({})"); + + EXPECT_EQ(should_fail && true, false) + << "Should have failed due to missing required field"; + + const auto homer = + rfl::json::read(R"({"first_name":"Homer"})").value(); + + EXPECT_EQ(Person{}.last_name.value(), "Simpson"); + EXPECT_EQ(homer.first_name, "Homer"); + EXPECT_EQ(homer.last_name.value(), "Simpson"); + EXPECT_EQ(homer.town.value(), ""); + + write_and_read( + Person{"Waylon", "Smith", "Springfield"}, + R"({"first_name":"Waylon","last_name":"Smith","town":"Springfield"})"); +} +} // namespace test_default_val diff --git a/tests/json/test_default_val_no_field_names.cpp b/tests/json/test_default_val_no_field_names.cpp new file mode 100644 index 00000000..1aac232b --- /dev/null +++ b/tests/json/test_default_val_no_field_names.cpp @@ -0,0 +1,35 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_default_val_no_field_names { + +struct Person { + std::string first_name; + rfl::DefaultVal last_name = "Simpson"; + rfl::DefaultVal town; +}; + +TEST(json, test_default_val_no_field_names) { + static_assert(rfl::internal::has_default_val_v, + "Should have default val"); + + const auto should_fail = rfl::json::read(R"([])"); + + EXPECT_EQ(should_fail && true, false) + << "Should have failed due to missing required field"; + + const auto homer = + rfl::json::read(R"(["Homer"])").value(); + + EXPECT_EQ(Person{}.last_name.value(), "Simpson"); + EXPECT_EQ(homer.first_name, "Homer"); + EXPECT_EQ(homer.last_name.value(), "Simpson"); + EXPECT_EQ(homer.town.value(), ""); + + write_and_read(Person{"Waylon", "Smith", "Springfield"}, + R"(["Waylon","Smith","Springfield"])"); +} +} // namespace test_default_val_no_field_names From c7e8b6e41fb46391c36a9129135e5899699a13f8 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 3 Dec 2025 23:07:34 +0100 Subject: [PATCH 2/9] Added missing headers --- include/rfl/DefaultVal.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/rfl/DefaultVal.hpp b/include/rfl/DefaultVal.hpp index 0af788f0..858db7a8 100644 --- a/include/rfl/DefaultVal.hpp +++ b/include/rfl/DefaultVal.hpp @@ -2,6 +2,9 @@ #define RFL_DEFAULTVAL_HPP_ #include +#include + +#include "default.hpp" namespace rfl { From 525df8b465b9d2af37544170b400f0bd7340568b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dr=2E=20Patrick=20Urbanke=20=28=E5=8A=89=E8=87=AA=E6=88=90?= =?UTF-8?q?=29?= Date: Wed, 3 Dec 2025 23:13:32 +0100 Subject: [PATCH 3/9] Update DefaultVal.hpp Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- include/rfl/DefaultVal.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rfl/DefaultVal.hpp b/include/rfl/DefaultVal.hpp index 858db7a8..15bd698c 100644 --- a/include/rfl/DefaultVal.hpp +++ b/include/rfl/DefaultVal.hpp @@ -32,7 +32,7 @@ struct DefaultVal { DefaultVal(const DefaultVal& _field) : value_(_field.get()) {} template - DefaultVal(DefaultVal&& _field) : value_(_field.get()) {} + DefaultVal(DefaultVal&& _field) noexcept(noexcept(Type(std::move(_field.value())))) : value_(std::move(_field.value())) {} template , bool>::type = true> From e1ef247943fbee8a50d36804cf0521fa4ab3ee25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dr=2E=20Patrick=20Urbanke=20=28=E5=8A=89=E8=87=AA=E6=88=90?= =?UTF-8?q?=29?= Date: Wed, 3 Dec 2025 23:13:45 +0100 Subject: [PATCH 4/9] Update DefaultVal.hpp Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- include/rfl/DefaultVal.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/rfl/DefaultVal.hpp b/include/rfl/DefaultVal.hpp index 15bd698c..7fc52c5d 100644 --- a/include/rfl/DefaultVal.hpp +++ b/include/rfl/DefaultVal.hpp @@ -108,9 +108,10 @@ struct DefaultVal { /// Assigns the underlying object. template auto& operator=(DefaultVal&& _field) { - value_ = std::forward(_field.value_); + value_ = std::move(_field.value_); return *this; } + } /// Assigns the underlying object. void set(const Type& _value) { value_ = _value; } From a247bc31ed00bde0a8d2736bffb1ba2b10884d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dr=2E=20Patrick=20Urbanke=20=28=E5=8A=89=E8=87=AA=E6=88=90?= =?UTF-8?q?=29?= Date: Wed, 3 Dec 2025 23:14:12 +0100 Subject: [PATCH 5/9] Update DefaultVal.hpp Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- include/rfl/DefaultVal.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/rfl/DefaultVal.hpp b/include/rfl/DefaultVal.hpp index 7fc52c5d..91d352cc 100644 --- a/include/rfl/DefaultVal.hpp +++ b/include/rfl/DefaultVal.hpp @@ -34,8 +34,8 @@ struct DefaultVal { template DefaultVal(DefaultVal&& _field) noexcept(noexcept(Type(std::move(_field.value())))) : value_(std::move(_field.value())) {} - template , - bool>::type = true> + template + requires(std::is_convertible_v) DefaultVal(const U& _value) : value_(_value) {} template , From 14bc974d9bf2528619f326441db4df7ee510ad83 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Thu, 4 Dec 2025 20:49:23 +0100 Subject: [PATCH 6/9] Fixed some minor errors --- include/rfl/DefaultVal.hpp | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/include/rfl/DefaultVal.hpp b/include/rfl/DefaultVal.hpp index 91d352cc..be6a6812 100644 --- a/include/rfl/DefaultVal.hpp +++ b/include/rfl/DefaultVal.hpp @@ -11,11 +11,6 @@ namespace rfl { template struct DefaultVal { public: - template - static StructType process(NamedTupleType&& _named_tuple) { - return _named_tuple; - } - using Type = std::remove_cvref_t; DefaultVal() : value_(Type()) {} @@ -32,24 +27,25 @@ struct DefaultVal { DefaultVal(const DefaultVal& _field) : value_(_field.get()) {} template - DefaultVal(DefaultVal&& _field) noexcept(noexcept(Type(std::move(_field.value())))) : value_(std::move(_field.value())) {} + DefaultVal(DefaultVal&& _field) noexcept( + noexcept(Type(std::move(_field.value())))) + : value_(std::move(_field.value())) {} template requires(std::is_convertible_v) DefaultVal(const U& _value) : value_(_value) {} - template , - bool>::type = true> + template + requires(std::is_convertible_v) DefaultVal(U&& _value) noexcept : value_(std::forward(_value)) {} - template , - bool>::type = true> + template + requires(std::is_convertible_v) DefaultVal(const DefaultVal& _field) : value_(_field.value()) {} /// Assigns the underlying object to its default value. - template , - bool>::type = true> + template + requires(std::is_default_constructible_v) DefaultVal(const Default&) : value_(Type()) {} ~DefaultVal() = default; @@ -108,10 +104,9 @@ struct DefaultVal { /// Assigns the underlying object. template auto& operator=(DefaultVal&& _field) { - value_ = std::move(_field.value_); + value_ = std::forward(_field.value_); return *this; } - } /// Assigns the underlying object. void set(const Type& _value) { value_ = _value; } From f3721fa966c117091e36523f3cf6436b38dfcec9 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Thu, 4 Dec 2025 20:49:28 +0100 Subject: [PATCH 7/9] Added documentation --- docs/default_val.md | 64 +++++++++++++++++++++++++++++++++++++++++++++ docs/index.md | 1 + 2 files changed, 65 insertions(+) create mode 100644 docs/default_val.md diff --git a/docs/default_val.md b/docs/default_val.md new file mode 100644 index 00000000..8c30ecf7 --- /dev/null +++ b/docs/default_val.md @@ -0,0 +1,64 @@ +# Default values (rfl::DefaultVal) + +The `rfl::DefaultVal` wrapper allows a struct field to have a predefined default value when serializing and deserializing. When a field is declared as `rfl::DefaultVal`, 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 last_name = "Simpson"; // has explicit default + rfl::DefaultVal 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(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(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`). diff --git a/docs/index.md b/docs/index.md index 5cbda969..6d1f6068 100644 --- a/docs/index.md +++ b/docs/index.md @@ -34,6 +34,7 @@ reflect-cpp fills an important gap in C++ development. It minimizes boilerplate - Simple [installation](https://rfl.getml.com/install) - Simple extendability to [other serialization formats](https://rfl.getml.com/supported_formats/supporting_your_own_format) - Simple extendability to [custom classes](https://rfl.getml.com/concepts/custom_classes) +- Support for default-valued fields via `rfl::DefaultVal` (see [Default values](default_val.md)) - Being one of the fastest serialization libraries in existence, as demonstrated by our [benchmarks](https://rfl.getml.com/benchmarks) From 95ce663582512e3a61feb7e968722f861ed5a464 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Thu, 4 Dec 2025 21:22:08 +0100 Subject: [PATCH 8/9] Added missing header --- include/rfl/parsing/NamedTupleParser.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/rfl/parsing/NamedTupleParser.hpp b/include/rfl/parsing/NamedTupleParser.hpp index 68d71b1f..7f423e04 100644 --- a/include/rfl/parsing/NamedTupleParser.hpp +++ b/include/rfl/parsing/NamedTupleParser.hpp @@ -10,6 +10,7 @@ #include "../NamedTuple.hpp" #include "../Result.hpp" #include "../always_false.hpp" +#include "../internal/has_default_val_v.hpp" #include "../internal/is_array.hpp" #include "../internal/is_attribute.hpp" #include "../internal/is_basic_type.hpp" From 23ce0b1f79bcdae501f3b3ce321d7d58653629d2 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Thu, 4 Dec 2025 21:43:47 +0100 Subject: [PATCH 9/9] Added more headers --- include/rfl/parsing/Parser_default_val.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/rfl/parsing/Parser_default_val.hpp b/include/rfl/parsing/Parser_default_val.hpp index 62147ea8..38f4b359 100644 --- a/include/rfl/parsing/Parser_default_val.hpp +++ b/include/rfl/parsing/Parser_default_val.hpp @@ -1,9 +1,14 @@ #ifndef RFL_PARSING_PARSER_DEFAULTVAL_HPP_ #define RFL_PARSING_PARSER_DEFAULTVAL_HPP_ +#include #include #include "../DefaultVal.hpp" +#include "AreReaderAndWriter.hpp" +#include "Parent.hpp" +#include "Parser_base.hpp" +#include "schema/Type.hpp" namespace rfl::parsing {