diff --git a/README.md b/README.md index 96b936f9..7a6c3f59 100644 --- a/README.md +++ b/README.md @@ -238,15 +238,30 @@ struct Person { std::vector children; }; -const auto fields = rfl::fields(); - -std::cout << "Fields in " << rfl::type_name_t().str() << ":" - << std::endl; -for (const auto& f : fields) { +for (const auto& f : rfl::fields()) { std::cout << "name: " << f.name() << ", type: " << f.type() << std::endl; } ``` +You can also create a view and then access these fields using `std::get` or `rfl::get`: + +```cpp +auto lisa = Person{.first_name = "Lisa", .last_name = "Simpson", .age = 8}; + +const auto view = rfl::to_view(lisa); + +// view.values() is a std::tuple containing +// pointers to the original fields. +// This will modify the struct `lisa`: +*std::get<0>(view.values()) = "Maggie"; + +// All of this is supported as well: +*view.get<1>() = "Simpson"; +*view.get<"age">() = 0; +*rfl::get<0>(view) = "Maggie"; +*rfl::get<"first_name">(view) = "Maggie"; +``` + It also possible to replace fields: ```cpp diff --git a/docs/README.md b/docs/README.md index 1d547811..afeef020 100644 --- a/docs/README.md +++ b/docs/README.md @@ -50,6 +50,8 @@ 4.3) [rfl::NamedTuple](https://github.com/getml/reflect-cpp/blob/main/docs/named_tuple.md) - For structural typing. +4.4) [rfl::to_view](https://github.com/getml/reflect-cpp/blob/main/docs/to_view.md) - For accessing fields of a struct by index or name. + ## 5) Supported formats 5.1) [JSON](https://github.com/getml/reflect-cpp/blob/main/docs/json.md) diff --git a/docs/to_view.md b/docs/to_view.md new file mode 100644 index 00000000..db474bcb --- /dev/null +++ b/docs/to_view.md @@ -0,0 +1,30 @@ +# `rfl::to_view` + +`rfl::to_view` allows you to create views on structs using which you can access an modify the fields of the structs just like a tuple. + +Under-the-hood, a view is a `rfl::NamedTuple` containing pointers to the original fields. + +For example: + +```cpp +auto lisa = Person{.first_name = "Lisa", .last_name = "Simpson", .age = 8}; + +const auto view = rfl::to_view(lisa); + +// Assigns the first field, thus modifying the struct 'lisa'. +*view.get<0>() = "Maggie"; + +// view.values() is a std::tuple containing +// pointers to the original fields. +*std::get<1>(view.values()) = "Simpson"; + +// You can also access fields by their name. +// The correctness will be ensured at compile time. +*view.get<"age">() = 0; + +// You can also access fields like this. +*rfl::get<0>(view) = "Maggie"; + +// Or like this. +*rfl::get<"first_name">(view) = "Maggie"; +``` \ No newline at end of file diff --git a/include/rfl.hpp b/include/rfl.hpp index 191b1da3..33d5ab28 100644 --- a/include/rfl.hpp +++ b/include/rfl.hpp @@ -46,6 +46,7 @@ #include "rfl/remove_fields.hpp" #include "rfl/replace.hpp" #include "rfl/to_named_tuple.hpp" +#include "rfl/to_view.hpp" #include "rfl/type_name_t.hpp" #include "rfl/visit.hpp" diff --git a/include/rfl/internal/bind_to_tuple.hpp b/include/rfl/internal/bind_to_tuple.hpp index 90c7cb6c..00a0bf5f 100644 --- a/include/rfl/internal/bind_to_tuple.hpp +++ b/include/rfl/internal/bind_to_tuple.hpp @@ -18,11 +18,12 @@ template constexpr auto tuple_view(T&); template -constexpr auto bind_to_tuple(T& _t, F&& f) { +constexpr auto bind_to_tuple(T& _t, const F& _f) { auto view = tuple_view(_t); return [&](std::index_sequence) { - return std::make_tuple(std::forward(f)(std::get(view))...); - }(std::make_index_sequence>()); + return std::make_tuple(_f(std::get(view))...); + } + (std::make_index_sequence>()); } /*The following boilerplate code was generated using a Python script: diff --git a/include/rfl/internal/to_flattened_ptr_tuple.hpp b/include/rfl/internal/to_flattened_ptr_tuple.hpp index 65962711..56f1b818 100644 --- a/include/rfl/internal/to_flattened_ptr_tuple.hpp +++ b/include/rfl/internal/to_flattened_ptr_tuple.hpp @@ -32,7 +32,7 @@ auto flatten_ptr_tuple(PtrTuple&& _t, Args... _args) { } template -auto to_flattened_ptr_tuple(const T& _t) { +auto to_flattened_ptr_tuple(T&& _t) { return flatten_ptr_tuple(to_ptr_tuple(_t)); } diff --git a/include/rfl/internal/to_ptr_named_tuple.hpp b/include/rfl/internal/to_ptr_named_tuple.hpp index d4b6c34e..31aaf3e7 100644 --- a/include/rfl/internal/to_ptr_named_tuple.hpp +++ b/include/rfl/internal/to_ptr_named_tuple.hpp @@ -19,7 +19,7 @@ namespace rfl { namespace internal { template -auto flatten_ptr_field_tuple(const PtrFieldTuple& _t, Args&&... _args) { +auto flatten_ptr_field_tuple(PtrFieldTuple& _t, Args&&... _args) { constexpr auto i = sizeof...(Args); if constexpr (i == std::tuple_size_v>) { return std::tuple_cat(std::forward(_args)...); @@ -38,7 +38,7 @@ auto flatten_ptr_field_tuple(const PtrFieldTuple& _t, Args&&... _args) { } template -auto field_tuple_to_named_tuple(const PtrFieldTuple& _ptr_field_tuple) { +auto field_tuple_to_named_tuple(PtrFieldTuple& _ptr_field_tuple) { const auto ft_to_nt = [](const Fields&... _fields) { return make_named_tuple(_fields...); }; @@ -54,19 +54,19 @@ auto field_tuple_to_named_tuple(const PtrFieldTuple& _ptr_field_tuple) { /// Generates a named tuple that contains pointers to the original values in /// the struct. template -auto to_ptr_named_tuple(const T& _t) { +auto to_ptr_named_tuple(T&& _t) { if constexpr (has_fields>()) { if constexpr (std::is_pointer_v>) { return to_ptr_named_tuple(*_t); } else if constexpr (is_named_tuple_v>) { return nt_to_ptr_named_tuple(_t); } else { - const auto ptr_field_tuple = to_ptr_field_tuple(_t); + auto ptr_field_tuple = to_ptr_field_tuple(_t); return field_tuple_to_named_tuple(ptr_field_tuple); } } else { using FieldNames = rfl::field_names_t; - const auto flattened_ptr_tuple = to_flattened_ptr_tuple(_t); + auto flattened_ptr_tuple = to_flattened_ptr_tuple(_t); return copy_flattened_tuple_to_named_tuple(flattened_ptr_tuple); } } diff --git a/include/rfl/internal/wrap_in_fields.hpp b/include/rfl/internal/wrap_in_fields.hpp index 8b098053..3a66952b 100644 --- a/include/rfl/internal/wrap_in_fields.hpp +++ b/include/rfl/internal/wrap_in_fields.hpp @@ -19,7 +19,7 @@ auto wrap_in_fields(auto&& _tuple, Fields&&... _fields) { return std::make_tuple(std::move(_fields)...); } else { auto value = std::move(std::get(_tuple)); - using Type = std::decay_t; + using Type = std::decay_t>; if constexpr (is_flatten_field_v) { // The problem here is that the FieldNames are already flattened, but this // is not, so we need to determine how many field names to skip. diff --git a/include/rfl/to_view.hpp b/include/rfl/to_view.hpp new file mode 100644 index 00000000..f94fd079 --- /dev/null +++ b/include/rfl/to_view.hpp @@ -0,0 +1,19 @@ +#ifndef RFL_TO_VIEW_HPP_ +#define RFL_TO_VIEW_HPP_ + +#include +#include +#include + +#include "rfl/internal/to_ptr_named_tuple.hpp" + +namespace rfl { + +template +auto to_view(T& _t) { + return internal::to_ptr_named_tuple(_t); +} + +} // namespace rfl + +#endif diff --git a/tests/json/test_view.cpp b/tests/json/test_view.cpp new file mode 100644 index 00000000..59e332d6 --- /dev/null +++ b/tests/json/test_view.cpp @@ -0,0 +1,34 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "test_replace.hpp" +#include "write_and_read.hpp" + +namespace test_view { + +struct Person { + std::string first_name; + std::string last_name; + int age; +}; + +void test() { + std::cout << std::source_location::current().function_name() << std::endl; + + auto lisa = Person{.first_name = "Lisa", .last_name = "Simpson", .age = 8}; + + const auto view = rfl::to_view(lisa); + + *view.get<0>() = "Maggie"; + *std::get<1>(view.values()) = "Simpson"; + *view.get<"age">() = 0; + + write_and_read(lisa, + R"({"first_name":"Maggie","last_name":"Simpson","age":0})"); +} +} // namespace test_view diff --git a/tests/json/test_view.hpp b/tests/json/test_view.hpp new file mode 100644 index 00000000..89812d89 --- /dev/null +++ b/tests/json/test_view.hpp @@ -0,0 +1,4 @@ +namespace test_view { +void test(); +} + diff --git a/tests/json/tests.cpp b/tests/json/tests.cpp index c69a0bc8..839744da 100644 --- a/tests/json/tests.cpp +++ b/tests/json/tests.cpp @@ -61,6 +61,7 @@ #include "test_unordered_multiset.hpp" #include "test_unordered_set.hpp" #include "test_variant.hpp" +#include "test_view.hpp" int main() { test_readme_example::test(); @@ -120,6 +121,7 @@ int main() { test_as::test(); test_as2::test(); test_as_flatten::test(); + test_view::test(); test_custom_constructor::test();