From fb0dd371b385624863ce57bc17a7541dd03ac4fb Mon Sep 17 00:00:00 2001 From: stu <s+github@stu.scot> Date: Wed, 7 Feb 2024 11:25:54 +0000 Subject: [PATCH 1/2] add rfl::NamedTuple::apply() for convenient compile-time iteration --- README.md | 8 +++++++- docs/named_tuple.md | 17 +++++++++++++++++ include/rfl/Field.hpp | 3 +++ include/rfl/NamedTuple.hpp | 20 ++++++++++++++++++++ tests/json/test_view.cpp | 19 +++++++++++++++++++ 5 files changed, 66 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 064d9266..3d79803a 100644 --- a/README.md +++ b/README.md @@ -281,7 +281,7 @@ for (const auto& f : rfl::fields<Person>()) { } ``` -You can also create a view and then access these fields using `std::get` or `rfl::get`: +You can also create a view and then access these fields using `std::get` or `rfl::get`, or iterate over the fields at compile-time: ```cpp auto lisa = Person{.first_name = "Lisa", .last_name = "Simpson", .age = 8}; @@ -298,6 +298,12 @@ const auto view = rfl::to_view(lisa); *view.get<"age">() = 0; *rfl::get<0>(view) = "Maggie"; *rfl::get<"first_name">(view) = "Maggie"; + +view.apply([](const auto& f) { + // f is a an rfl::Field pointing to the original field. + // f.name() is a compile-time constant. + std::cout << f.name() << ": " << rfl::json::write(*f.value()) << std::endl; +}); ``` It also possible to replace fields: diff --git a/docs/named_tuple.md b/docs/named_tuple.md index f12f2ff4..d1f99e5c 100644 --- a/docs/named_tuple.md +++ b/docs/named_tuple.md @@ -95,6 +95,23 @@ const auto first_name = person.template get<"firstName">(); const auto first_name = rfl::get<"firstName">(person); ``` +Fields can also be iterated over at compile-time using the `apply()` method: + +```cpp +auto person = rfl::Field<"first_name", std::string>("Bart") * + rfl::Field<"last_name", std::string>("Simpson"); + +person.apply([](const auto& f) { + constexpr auto field_name = f.name(); + const auto& value = *f.value(); +}); + +person.apply([]<typename Field>(Field& f) { + using field_pointer_type = typename Field::Type; + field_pointer_type* value = f.value(); +}); +``` + ### `rfl::replace` `rfl::replace` works for `rfl::NamedTuple` as well: diff --git a/include/rfl/Field.hpp b/include/rfl/Field.hpp index ae190b30..720b6de2 100644 --- a/include/rfl/Field.hpp +++ b/include/rfl/Field.hpp @@ -62,6 +62,9 @@ struct Field { /// The name of the field, for internal use. constexpr static const internal::StringLiteral name_ = _name; + /// The name of the field. + constexpr static std::string_view name() { return name_.string_view(); } + /// Returns the underlying object. const Type& get() const { return value_; } diff --git a/include/rfl/NamedTuple.hpp b/include/rfl/NamedTuple.hpp index 3a4cbfec..0927827a 100644 --- a/include/rfl/NamedTuple.hpp +++ b/include/rfl/NamedTuple.hpp @@ -210,6 +210,26 @@ class NamedTuple { return rfl::make_field<_field_name>(rfl::get<_field_name>(*this)); } + /// Invokes a callable object once for each field in order. + template <typename F> + void apply(F&& f) { + std::apply( + [&f]<typename... AFields>(AFields&&... fields) { + ((f(std::forward<AFields>(fields))), ...); + }, + fields()); + } + + /// Invokes a callable object once for each field in order. + template <typename F> + void apply(F&& f) const { + std::apply( + [&f]<typename... AFields>(AFields&&... fields) { + ((f(std::forward<AFields>(fields))), ...); + }, + fields()); + } + /// Copy assignment operator. NamedTuple<FieldTypes...>& operator=( const NamedTuple<FieldTypes...>& _other) = default; diff --git a/tests/json/test_view.cpp b/tests/json/test_view.cpp index 59e332d6..b9e6a292 100644 --- a/tests/json/test_view.cpp +++ b/tests/json/test_view.cpp @@ -4,6 +4,7 @@ #include <rfl/json.hpp> #include <source_location> #include <string> +#include <type_traits> #include <vector> #include "test_replace.hpp" @@ -30,5 +31,23 @@ void test() { write_and_read(lisa, R"({"first_name":"Maggie","last_name":"Simpson","age":0})"); + + view.apply([]<typename Field>(const Field& field) { + using field_type = std::remove_pointer_t<typename Field::Type>; + if constexpr (field.name() == "age") { + static_assert(std::is_same_v<field_type, int>); + } else { + static_assert(std::is_same_v<field_type, std::string>); + } + }); + + rfl::to_view(lisa).apply([](auto&& field) { + if constexpr (field.name() == "first_name") { + *field.value() = "Bart"; + } + }); + + write_and_read(lisa, + R"({"first_name":"Bart","last_name":"Simpson","age":0})"); } } // namespace test_view From 34c36d9b97f0c87e245fbf74ed6debf43555385b Mon Sep 17 00:00:00 2001 From: stu <s+github@stu.scot> Date: Wed, 7 Feb 2024 13:01:06 +0000 Subject: [PATCH 2/2] slight rework clang on linux doesn't believe field.name() can be constexpr, so fall back to Field::name() when constexpr is required. --- README.md | 1 - docs/named_tuple.md | 4 +++- tests/json/test_view.cpp | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3d79803a..843c5ce9 100644 --- a/README.md +++ b/README.md @@ -301,7 +301,6 @@ const auto view = rfl::to_view(lisa); view.apply([](const auto& f) { // f is a an rfl::Field pointing to the original field. - // f.name() is a compile-time constant. std::cout << f.name() << ": " << rfl::json::write(*f.value()) << std::endl; }); ``` diff --git a/docs/named_tuple.md b/docs/named_tuple.md index d1f99e5c..0785f92b 100644 --- a/docs/named_tuple.md +++ b/docs/named_tuple.md @@ -102,11 +102,13 @@ auto person = rfl::Field<"first_name", std::string>("Bart") * rfl::Field<"last_name", std::string>("Simpson"); person.apply([](const auto& f) { - constexpr auto field_name = f.name(); + auto field_name = f.name(); const auto& value = *f.value(); }); person.apply([]<typename Field>(Field& f) { + // The field name can also be obtained as a compile-time constant. + constexpr auto field_name = Field::name(); using field_pointer_type = typename Field::Type; field_pointer_type* value = f.value(); }); diff --git a/tests/json/test_view.cpp b/tests/json/test_view.cpp index b9e6a292..b09622c7 100644 --- a/tests/json/test_view.cpp +++ b/tests/json/test_view.cpp @@ -34,15 +34,15 @@ void test() { view.apply([]<typename Field>(const Field& field) { using field_type = std::remove_pointer_t<typename Field::Type>; - if constexpr (field.name() == "age") { + if constexpr (Field::name() == "age") { static_assert(std::is_same_v<field_type, int>); } else { static_assert(std::is_same_v<field_type, std::string>); } }); - rfl::to_view(lisa).apply([](auto&& field) { - if constexpr (field.name() == "first_name") { + rfl::to_view(lisa).apply([](auto field) { + if constexpr (decltype(field)::name() == "first_name") { *field.value() = "Bart"; } });