Skip to content
Open
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
50 changes: 50 additions & 0 deletions docs/content/custom_classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,53 @@ class Person {
int age;
};
```

## Example 4: Keeping your class an aggregate

Declaring a constructor makes your class a non-aggregate, which costs you
designated-initializer and aggregate initialization:

```cpp
struct Config {
Config(ReflectionType&&); // this makes Config a non-aggregate...
int port;
bool tls;
};

// ...so this no longer compiles
auto config = Config{.port = 443, .tls = true};
```

If you want to keep those, provide a static `from_reflection()` factory instead
of a constructor:

```cpp
struct Config {
struct ConfigImpl {
rfl::Rename<"portNumber", int> port;
bool tls;
};

// 1) Publicly define `ReflectionType`
using ReflectionType = ConfigImpl;

// 2) A static factory, instead of a constructor
static Config from_reflection(const ReflectionType& _impl) {
return Config{.port = _impl.port(), .tls = _impl.tls};
}

// 3) Method called `reflection` that returns `ReflectionType`
ReflectionType reflection() const {
return ReflectionType{.port = port, .tls = tls};
}

int port;
bool tls;
};

// Config is still an aggregate, so this keeps working
auto config = Config{.port = 443, .tls = true};
```

If a class provides both a `from_reflection()` factory and a converting
constructor, the factory is used.
17 changes: 17 additions & 0 deletions include/rfl/internal/HasFromReflectionMethod.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#ifndef RFL_INTERNAL_HASFROMREFLECTIONMETHOD_HPP_
#define RFL_INTERNAL_HASFROMREFLECTIONMETHOD_HPP_

#include <concepts>
#include <utility>

namespace rfl::internal {

/// Satisfied by a type `T` providing `static T from_reflection(ReflectionType&&)`.
template <class T>
concept HasFromReflectionMethod = requires(typename T::ReflectionType&& _r) {
{ T::from_reflection(std::move(_r)) } -> std::convertible_to<T>;
};

} // namespace rfl::internal

#endif
7 changes: 6 additions & 1 deletion include/rfl/parsing/Parser_default.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "../Result.hpp"
#include "../always_false.hpp"
#include "../internal/HasFromReflectionMethod.hpp"
#include "../internal/default_if_missing_v.hpp"
#include "../internal/has_default_val_v.hpp"
#include "../internal/has_reflection_method_v.hpp"
Expand Down Expand Up @@ -242,7 +243,11 @@ struct Parser {
const auto wrap_in_t = [](auto&& _named_tuple) -> Result<T> {
try {
using NT = decltype(_named_tuple);
return T{std::forward<NT>(_named_tuple)};
if constexpr (internal::HasFromReflectionMethod<T>) {
return T::from_reflection(std::forward<NT>(_named_tuple));
} else {
return T{std::forward<NT>(_named_tuple)};
}
} catch (std::exception& e) {
return error(e.what());
}
Expand Down
51 changes: 51 additions & 0 deletions tests/json/test_custom_class5.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <string>
#include <type_traits>
#include <vector>

#include "write_and_read.hpp"

namespace test_custom_class5 {

/// Uses a static `from_reflection` factory instead of a converting
/// constructor. Because no constructor is declared, `Config` remains an
/// aggregate and can still be built with a designated initializer list.
struct Config {
struct ConfigImpl {
rfl::Rename<"portNumber", int> port;
std::string host = "localhost";
std::vector<std::string> tags;
};

using ReflectionType = ConfigImpl;

static Config from_reflection(ReflectionType&& _impl) {
return Config{.port = _impl.port(),
.host = _impl.host,
.tags = _impl.tags};
}

ReflectionType reflection() const {
return ReflectionType{.port = port, .host = host, .tags = tags};
}

int port;
std::string host;
std::vector<std::string> tags;
};

// The whole point of the factory: no constructor is declared, so designated
// initializers keep working.
static_assert(std::is_aggregate_v<Config>);

TEST(json, test_custom_class5) {
const auto config =
Config{.port = 8080, .host = "example.com", .tags = {"a", "b"}};

write_and_read(
config,
R"({"portNumber":8080,"host":"example.com","tags":["a","b"]})");
}

} // namespace test_custom_class5
Loading