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";
     }
   });