Skip to content

Commit 0c4de4f

Browse files
Merge branch 'main' into nulltermination
2 parents a36074d + 36bc907 commit 0c4de4f

36 files changed

Lines changed: 4961 additions & 2893 deletions

.github/workflows/linux-conan.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ on: [ push, pull_request ]
55
jobs:
66
linux:
77
name: "${{ github.job }} (Conan)"
8+
concurrency:
9+
group: "linux-${{ github.ref }}-${{ github.job }}-conan"
10+
cancel-in-progress: true
811
runs-on: ubuntu-24.04
912
steps:
1013
- name: Checkout

.github/workflows/linux.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ jobs:
4545
compiler-version: 17
4646
cxx: 23
4747
name: "${{ github.job }} (${{ matrix.format }}-C++${{ matrix.cxx }}-${{ matrix.compiler }}-${{ matrix.compiler-version }})"
48+
concurrency:
49+
group: "linux-${{ github.ref }}-${{ github.job }}-${{ matrix.format }}-C++${{ matrix.cxx }}-${{ matrix.compiler }}-${{ matrix.compiler-version }}"
50+
cancel-in-progress: true
4851
runs-on: ubuntu-24.04
4952
steps:
5053
- name: Checkout

.github/workflows/macos.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ jobs:
1313
os: ["macos-latest", "macos-13"]
1414
format: ["tests", "benchmarks"]
1515
name: "${{ matrix.os }} (${{ matrix.format }})"
16+
concurrency:
17+
group: "macos-${{ github.ref }}-${{ github.job }}-${{ matrix.os }}-${{ matrix.format }}"
18+
cancel-in-progress: true
1619
runs-on: ${{ matrix.os }}
1720
steps:
1821
- name: Checkout

.github/workflows/windows.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ jobs:
1212
matrix:
1313
format: ["JSON", "AVRO", "CAPNPROTO", "CBOR", "FLEXBUFFERS", "MSGPACK", "PARQUET", "TOML", "UBJSON", "XML", "YAML", "benchmarks"]
1414
name: "windows-msvc (${{ matrix.format }})"
15+
concurrency:
16+
group: "windows-${{ github.ref }}-${{ github.job }}-${{ matrix.format }}"
17+
cancel-in-progress: true
1518
runs-on: windows-latest
1619
steps:
1720
- name: Checkout

docs/default_val.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Default values (rfl::DefaultVal)
2+
3+
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).
4+
5+
## Declaration and initialization
6+
7+
You can declare a default-valued field like this:
8+
9+
```cpp
10+
struct Person {
11+
std::string first_name; // required
12+
rfl::DefaultVal<std::string> last_name = "Simpson"; // has explicit default
13+
rfl::DefaultVal<std::string> town; // default-constructed (empty string)
14+
};
15+
```
16+
17+
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):
18+
19+
```cpp
20+
Person p;
21+
p.last_name = "Smith"; // assign underlying value
22+
p.town = rfl::Default{}; // reset to default (empty string)
23+
std::string s = p.last_name.value();
24+
```
25+
26+
API convenience:
27+
28+
- .get(), .value(), operator()() — access the underlying value (const and non-const overloads).
29+
- set(...) — assign underlying value.
30+
31+
## JSON behaviour
32+
33+
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).
34+
35+
Example (object fields):
36+
37+
```cpp
38+
// Person from above
39+
const auto homer = rfl::json::read<Person>(R"({"first_name":"Homer"})").value();
40+
// homer.last_name == "Simpson" (declared default)
41+
// homer.town == "" (default-constructed)
42+
```
43+
44+
Example (no field names / positional arrays):
45+
46+
DefaultVal also works when using rfl::NoFieldNames (positional JSON arrays). Omitted positions that correspond to DefaultVal fields get their default values:
47+
48+
```cpp
49+
const auto homer = rfl::json::read<Person, rfl::NoFieldNames>(R"(["Homer"])").value();
50+
// homer.first_name == "Homer"
51+
// homer.last_name == "Simpson"
52+
// homer.town == ""
53+
```
54+
55+
## When to use
56+
57+
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).
58+
59+
## Notes
60+
61+
- The underlying type must be default-constructible to allow resetting via `rfl::Default` or when no explicit default is supplied.
62+
- 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`).
63+
64+
For more advanced control over when fields are considered missing and how defaults are applied, see the processors documentation (e.g., `rfl::DefaultIfMissing`).

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ reflect-cpp fills an important gap in C++ development. It minimizes boilerplate
3434
- Simple [installation](https://rfl.getml.com/install)
3535
- Simple extendability to [other serialization formats](https://rfl.getml.com/supported_formats/supporting_your_own_format)
3636
- Simple extendability to [custom classes](https://rfl.getml.com/concepts/custom_classes)
37+
- Support for default-valued fields via `rfl::DefaultVal` (see [Default values](default_val.md))
3738
- Being one of the fastest serialization libraries in existence, as demonstrated by our [benchmarks](https://rfl.getml.com/benchmarks)
3839

3940

include/rfl.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "rfl/Box.hpp"
1818
#include "rfl/Bytestring.hpp"
1919
#include "rfl/DefaultIfMissing.hpp"
20+
#include "rfl/DefaultVal.hpp"
2021
#include "rfl/Description.hpp"
2122
#include "rfl/ExtraFields.hpp"
2223
#include "rfl/Field.hpp"

include/rfl/DefaultVal.hpp

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#ifndef RFL_DEFAULTVAL_HPP_
2+
#define RFL_DEFAULTVAL_HPP_
3+
4+
#include <type_traits>
5+
#include <utility>
6+
7+
#include "default.hpp"
8+
9+
namespace rfl {
10+
11+
template <class T>
12+
struct DefaultVal {
13+
public:
14+
using Type = std::remove_cvref_t<T>;
15+
16+
DefaultVal() : value_(Type()) {}
17+
18+
DefaultVal(const Type& _value) : value_(_value) {}
19+
20+
DefaultVal(Type&& _value) noexcept : value_(std::move(_value)) {}
21+
22+
DefaultVal(DefaultVal&& _field) noexcept = default;
23+
24+
DefaultVal(const DefaultVal& _field) = default;
25+
26+
template <class U>
27+
DefaultVal(const DefaultVal<U>& _field) : value_(_field.get()) {}
28+
29+
template <class U>
30+
DefaultVal(DefaultVal<U>&& _field) noexcept(
31+
noexcept(Type(std::move(_field.value()))))
32+
: value_(std::move(_field.value())) {}
33+
34+
template <class U>
35+
requires(std::is_convertible_v<U, Type>)
36+
DefaultVal(const U& _value) : value_(_value) {}
37+
38+
template <class U>
39+
requires(std::is_convertible_v<U, Type>)
40+
DefaultVal(U&& _value) noexcept : value_(std::forward<U>(_value)) {}
41+
42+
template <class U>
43+
requires(std::is_convertible_v<U, Type>)
44+
DefaultVal(const DefaultVal<U>& _field) : value_(_field.value()) {}
45+
46+
/// Assigns the underlying object to its default value.
47+
template <class U = Type>
48+
requires(std::is_default_constructible_v<U>)
49+
DefaultVal(const Default&) : value_(Type()) {}
50+
51+
~DefaultVal() = default;
52+
53+
/// Returns the underlying object.
54+
const Type& get() const { return value_; }
55+
56+
/// Returns the underlying object.
57+
Type& operator()() { return value_; }
58+
59+
/// Returns the underlying object.
60+
const Type& operator()() const { return value_; }
61+
62+
/// Assigns the underlying object.
63+
auto& operator=(const Type& _value) {
64+
value_ = _value;
65+
return *this;
66+
}
67+
68+
/// Assigns the underlying object.
69+
auto& operator=(Type&& _value) noexcept {
70+
value_ = std::move(_value);
71+
return *this;
72+
}
73+
74+
/// Assigns the underlying object.
75+
template <class U, typename std::enable_if<std::is_convertible_v<U, Type>,
76+
bool>::type = true>
77+
auto& operator=(const U& _value) {
78+
value_ = _value;
79+
return *this;
80+
}
81+
82+
/// Assigns the underlying object to its default value.
83+
template <class U = Type,
84+
typename std::enable_if<std::is_default_constructible_v<U>,
85+
bool>::type = true>
86+
auto& operator=(const Default&) {
87+
value_ = Type();
88+
return *this;
89+
}
90+
91+
/// Assigns the underlying object.
92+
DefaultVal& operator=(const DefaultVal& _field) = default;
93+
94+
/// Assigns the underlying object.
95+
DefaultVal& operator=(DefaultVal&& _field) = default;
96+
97+
/// Assigns the underlying object.
98+
template <class U>
99+
auto& operator=(const DefaultVal<U>& _field) {
100+
value_ = _field.get();
101+
return *this;
102+
}
103+
104+
/// Assigns the underlying object.
105+
template <class U>
106+
auto& operator=(DefaultVal<U>&& _field) {
107+
value_ = std::forward<U>(_field.value_);
108+
return *this;
109+
}
110+
111+
/// Assigns the underlying object.
112+
void set(const Type& _value) { value_ = _value; }
113+
114+
/// Assigns the underlying object.
115+
void set(Type&& _value) { value_ = std::move(_value); }
116+
117+
/// Returns the underlying object.
118+
Type& value() { return value_; }
119+
120+
/// Returns the underlying object.
121+
const Type& value() const { return value_; }
122+
123+
/// The underlying value.
124+
Type value_;
125+
};
126+
127+
} // namespace rfl
128+
129+
#endif

include/rfl/Generic.hpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,23 @@ class RFL_API Generic {
126126
}
127127

128128
/// Casts the underlying value to a double or returns an rfl::Error, if the
129-
/// underlying value is not a double.
129+
/// underlying value is not a number or the conversion would result in loss of
130+
/// precision.
130131
Result<double> to_double() const noexcept {
131132
return std::visit(
132133
[](auto _v) -> Result<double> {
133134
using V = std::remove_cvref_t<decltype(_v)>;
134135
if constexpr (std::is_same_v<V, double>) {
135136
return _v;
137+
} else if constexpr (std::is_same_v<V, int64_t>) {
138+
auto _d = static_cast<double>(_v);
139+
if (static_cast<int64_t>(_d) == _v) {
140+
return _d;
141+
} else {
142+
return error(
143+
"rfl::Generic: Could not cast the underlying value to a "
144+
"double without loss of precision.");
145+
}
136146
} else {
137147
return error(
138148
"rfl::Generic: Could not cast the underlying value to a "

include/rfl/Literal.hpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,6 @@ class Literal {
4545

4646
Literal(const std::string& _str) : value_(find_value(_str).value()) {}
4747

48-
/// A single-field literal is special because it
49-
/// can also have a default constructor.
50-
template <ValueType num_fields = num_fields_,
51-
typename = std::enable_if_t<num_fields <= 1>>
5248
Literal() : value_(0) {}
5349

5450
~Literal() = default;

0 commit comments

Comments
 (0)