Skip to content

Commit fdf05d6

Browse files
Merge branch 'main' into f/init_struct
2 parents 4a0cb70 + e01b193 commit fdf05d6

22 files changed

Lines changed: 464 additions & 17 deletions

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,7 @@ reflect-cpp supports the following containers from the C++ standard library:
621621
- `std::chrono::duration`
622622
- `std::filesystem::path`
623623
- `std::forward_list`
624+
- `std::inplace_vector` (C++26)
624625
- `std::map`
625626
- `std::multimap`
626627
- `std::multiset`

‎docs/content/custom_classes.md‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,53 @@ class Person {
129129
int age;
130130
};
131131
```
132+
133+
## Example 4: Keeping your class an aggregate
134+
135+
Declaring a constructor makes your class a non-aggregate, which costs you
136+
designated-initializer and aggregate initialization:
137+
138+
```cpp
139+
struct Config {
140+
Config(ReflectionType&&); // this makes Config a non-aggregate...
141+
int port;
142+
bool tls;
143+
};
144+
145+
// ...so this no longer compiles
146+
auto config = Config{.port = 443, .tls = true};
147+
```
148+
149+
If you want to keep those, provide a static `from_reflection()` factory instead
150+
of a constructor:
151+
152+
```cpp
153+
struct Config {
154+
struct ConfigImpl {
155+
rfl::Rename<"portNumber", int> port;
156+
bool tls;
157+
};
158+
159+
// 1) Publicly define `ReflectionType`
160+
using ReflectionType = ConfigImpl;
161+
162+
// 2) A static factory, instead of a constructor
163+
static Config from_reflection(const ReflectionType& _impl) {
164+
return Config{.port = _impl.port(), .tls = _impl.tls};
165+
}
166+
167+
// 3) Method called `reflection` that returns `ReflectionType`
168+
ReflectionType reflection() const {
169+
return ReflectionType{.port = port, .tls = tls};
170+
}
171+
172+
int port;
173+
bool tls;
174+
};
175+
176+
// Config is still an aggregate, so this keeps working
177+
auto config = Config{.port = 443, .tls = true};
178+
```
179+
180+
If a class provides both a `from_reflection()` factory and a converting
181+
constructor, the factory is used.

‎docs/content/processors.md‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ reflect-cpp currently supports the following processors:
3838
- `rfl::AddNamespacedTagsToVariants`
3939
- `rfl::AllowRawPtrs`
4040
- `rfl::DefaultIfMissing`
41+
- `rfl::DurationsAsCount`
4142
- `rfl::EnumNamesOnly`
4243
- `rfl::NoExtraFields`
4344
- `rfl::NoFieldNames`
@@ -290,6 +291,27 @@ have gotten had you read the following JSON string:
290291
Because you have not passed a default value to town, the default value
291292
of the type is used instead.
292293

294+
### `rfl::DurationsAsCount`
295+
296+
By default, `std::chrono::duration` types are written as an object containing
297+
the count and the unit. By passing the processor `rfl::DurationsAsCount`,
298+
they will be written and read as a plain count in the unit of the C++ type:
299+
300+
```cpp
301+
struct MyStruct {
302+
std::chrono::milliseconds timeout;
303+
};
304+
305+
rfl::json::write<rfl::DurationsAsCount>(
306+
MyStruct{.timeout = std::chrono::milliseconds(1500)});
307+
```
308+
309+
The resulting JSON string looks like this:
310+
311+
```json
312+
{"timeout":1500}
313+
```
314+
293315
### `rfl::EnumNamesOnly`
294316

295317
By default, when reading an enum from a string, numeric values are accepted

‎docs/content/timestamps.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,7 @@ The following units are supported: `std::chrono::nanoseconds`, `std::chrono::mic
116116
`std::chrono::milliseconds`, `std::chrono::seconds`,
117117
`std::chrono::minutes`, `std::chrono::hours`, `std::chrono::days`,
118118
`std::chrono::weeks`, `std::chrono::months`, and `std::chrono::years`.
119+
120+
To write and read durations as a plain count in the unit of the C++ type
121+
(e.g. `{"duration":10}`), pass the `rfl::DurationsAsCount` processor
122+
(see [processors](../processors)).

‎include/rfl.hpp‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "rfl/DefaultIfMissing.hpp"
2222
#include "rfl/DefaultVal.hpp"
2323
#include "rfl/Description.hpp"
24+
#include "rfl/DurationsAsCount.hpp"
2425
#include "rfl/EnumNamesOnly.hpp"
2526
#include "rfl/ExtraFields.hpp"
2627
#include "rfl/Field.hpp"

‎include/rfl/DurationsAsCount.hpp‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#ifndef RFL_DURATIONSASCOUNT_HPP_
2+
#define RFL_DURATIONSASCOUNT_HPP_
3+
4+
namespace rfl {
5+
6+
/// This is a 'fake' processor - it doesn't do much by itself, but its
7+
/// inclusion instructs parsers to write and read std::chrono::duration types
8+
/// as a plain count in the unit of the C++ type, instead of {count, unit}.
9+
struct DurationsAsCount {
10+
public:
11+
template <class StructType>
12+
static auto process(auto&& _named_tuple) {
13+
return _named_tuple;
14+
}
15+
};
16+
17+
} // namespace rfl
18+
19+
#endif
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#ifndef RFL_INTERNAL_HASFROMREFLECTIONMETHOD_HPP_
2+
#define RFL_INTERNAL_HASFROMREFLECTIONMETHOD_HPP_
3+
4+
#include <concepts>
5+
#include <utility>
6+
7+
namespace rfl::internal {
8+
9+
/// Satisfied by a type `T` providing `static T from_reflection(ReflectionType&&)`.
10+
template <class T>
11+
concept HasFromReflectionMethod = requires(typename T::ReflectionType&& _r) {
12+
{ T::from_reflection(std::move(_r)) } -> std::convertible_to<T>;
13+
};
14+
15+
} // namespace rfl::internal
16+
17+
#endif
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#ifndef RFL_INTERNAL_DURATIONSASCOUNT_HPP_
2+
#define RFL_INTERNAL_DURATIONSASCOUNT_HPP_
3+
4+
#include <type_traits>
5+
6+
#include "../Processors.hpp"
7+
#include "../DurationsAsCount.hpp"
8+
9+
namespace rfl::internal {
10+
11+
template <class T>
12+
class durations_as_count;
13+
14+
template <class T>
15+
class durations_as_count : public std::false_type {};
16+
17+
template <>
18+
class durations_as_count<DurationsAsCount> : public std::true_type {};
19+
20+
template <class Head, class... Tail>
21+
struct durations_as_count<Processors<Head, Tail...>> {
22+
static constexpr bool value =
23+
(durations_as_count<Head>::value || ... || durations_as_count<Tail>::value);
24+
};
25+
26+
template <class T>
27+
constexpr bool durations_as_count_v =
28+
durations_as_count<std::remove_cvref_t<std::remove_pointer_t<T>>>::value;
29+
30+
} // namespace rfl::internal
31+
32+
#endif

‎include/rfl/parsing/NamedTupleParser.hpp‎

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@
1111
#include "../Result.hpp"
1212
#include "../internal/default_if_missing_v.hpp"
1313
#include "../internal/has_default_val_v.hpp"
14+
#include "../internal/has_reflection_method_v.hpp"
15+
#include "../internal/has_reflection_type_v.hpp"
1416
#include "../internal/is_attribute.hpp"
1517
#include "../internal/is_default_val_v.hpp"
1618
#include "../internal/is_extra_fields.hpp"
1719
#include "../internal/is_skip.hpp"
1820
#include "../internal/nth_element_t.hpp"
21+
#include "../internal/ptr_cast.hpp"
22+
#include "../num_fields.hpp"
1923
#include "../to_view.hpp"
2024
#include "AreReaderAndWriter.hpp"
2125
#include "Parent.hpp"
@@ -35,6 +39,35 @@ namespace rfl {
3539
template <class... Ps>
3640
Generic to_generic(const auto& _t);
3741

42+
namespace internal {
43+
44+
/// Satisfied by a type that carries a nested `ReflectionType` and can be
45+
/// unwrapped by decomposing it, like `rfl::Validator`.
46+
template <class T>
47+
concept IsReflectionTypeWrapper =
48+
// We use `std::is_aggregate_v` and `num_fields` rather than `requires`
49+
// because `requires`, well, requires an expression, but checking this via
50+
// syntax would require a statement `const auto& [_] = _t`, which is not
51+
// allowed in a `requires` clause.
52+
has_reflection_type_v<T> && !has_reflection_method_v<T> &&
53+
std::is_aggregate_v<T> && num_fields<T> == 1;
54+
55+
/// Satisfied when the generic writer can serialize `T`, i.e. when a schema
56+
/// `default` can be produced for it. Every type is writable, except those that
57+
/// can be constructed from `ReflectionType`, but not converted back to it.
58+
template <class T>
59+
concept CanWriteGeneric =
60+
!has_reflection_type_v<std::remove_cvref_t<T>> ||
61+
has_reflection_method_v<std::remove_cvref_t<T>> ||
62+
IsReflectionTypeWrapper<std::remove_cvref_t<T>>;
63+
64+
/// Satisfied when a schema `default` can be emitted for the field type `T`:
65+
/// it must be a `DefaultVal` that satisfies `CanWriteGeneric`.
66+
template <class T>
67+
concept CanEmitDefault =
68+
is_default_val_v<T> && CanWriteGeneric<typename T::Type>;
69+
70+
} // namespace internal
3871
} // namespace rfl
3972

4073
namespace rfl::parsing {
@@ -235,12 +268,12 @@ struct NamedTupleParser {
235268
if constexpr (!internal::is_skip_v<U> && !internal::is_extra_fields_v<U>) {
236269
// Add default value here
237270
auto s = Parser<R, W, U, ProcessorsType>::to_schema(_definitions);
238-
if constexpr (!std::is_same_v<View, void>) {
271+
if constexpr (!std::is_same_v<View, void> && internal::CanEmitDefault<U>) {
239272
s.variant_.visit([&](auto& value) {
240273
if constexpr (std::is_same_v<std::remove_cvref_t<decltype(value)>,
241274
schema::Type::DefaultVal>) {
242275
value.default_value_ =
243-
rfl::to_generic((*rfl::get<_i>(*_view)).get());
276+
rfl::to_generic<ProcessorsType>((*rfl::get<_i>(*_view)).get());
244277
}
245278
});
246279
}

‎include/rfl/parsing/ParserDuration.hpp‎

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "../Literal.hpp"
99
#include "../Variant.hpp"
1010
#include "../always_false.hpp"
11+
#include "../internal/durations_as_count_v.hpp"
1112
#include "Parent.hpp"
1213
#include "Parser_base.hpp"
1314
#include "schema/Type.hpp"
@@ -55,13 +56,18 @@ struct ParserDuration {
5556
*/
5657
static Result<DurationType> read(const R& _r,
5758
const InputVarType& _var) noexcept {
58-
return Parser<R, W, RType, ProcessorsType>::read(_r, _var)
59-
.and_then(to_duration)
60-
.transform([](auto&& _duration) {
61-
return _duration.visit([](auto&& _d) -> DurationType {
62-
return std::chrono::duration_cast<DurationType>(std::move(_d));
59+
if constexpr (internal::durations_as_count_v<ProcessorsType>) {
60+
return Parser<R, W, Rep, ProcessorsType>::read(_r, _var).transform(
61+
[](const Rep _count) { return DurationType(_count); });
62+
} else {
63+
return Parser<R, W, RType, ProcessorsType>::read(_r, _var)
64+
.and_then(to_duration)
65+
.transform([](auto&& _duration) {
66+
return _duration.visit([](auto&& _d) -> DurationType {
67+
return std::chrono::duration_cast<DurationType>(std::move(_d));
68+
});
6369
});
64-
});
70+
}
6571
}
6672

6773
/**
@@ -74,9 +80,13 @@ struct ParserDuration {
7480
*/
7581
template <class P>
7682
static void write(const W& _w, const DurationType& _d, const P& _parent) {
77-
const auto r =
78-
RType{.count = static_cast<int64_t>(_d.count()), .unit = make_unit()};
79-
return Parser<R, W, RType, ProcessorsType>::write(_w, r, _parent);
83+
if constexpr (internal::durations_as_count_v<ProcessorsType>) {
84+
Parser<R, W, Rep, ProcessorsType>::write(_w, _d.count(), _parent);
85+
} else {
86+
const auto r = RType{.count = static_cast<int64_t>(_d.count()),
87+
.unit = make_unit()};
88+
Parser<R, W, RType, ProcessorsType>::write(_w, r, _parent);
89+
}
8090
}
8191

8292
/**
@@ -87,7 +97,11 @@ struct ParserDuration {
8797
*/
8898
static schema::Type to_schema(
8999
std::map<std::string, schema::Type>* _definitions) {
90-
return Parser<R, W, RType, ProcessorsType>::to_schema(_definitions);
100+
if constexpr (internal::durations_as_count_v<ProcessorsType>) {
101+
return Parser<R, W, Rep, ProcessorsType>::to_schema(_definitions);
102+
} else {
103+
return Parser<R, W, RType, ProcessorsType>::to_schema(_definitions);
104+
}
91105
}
92106

93107
private:

0 commit comments

Comments
 (0)