getml / reflect-cpp

A C++20 library for fast serialization, deserialization and validation using reflection. Supports JSON, BSON, CBOR, flexbuffers, msgpack, TOML, XML, YAML / msgpack.org[C++20]
https://getml.github.io/reflect-cpp/
MIT License
901 stars 76 forks source link

Q: how to get the non-const reference of a struct field? #32

Closed lixin-wei closed 8 months ago

lixin-wei commented 8 months ago

I want something like rfl::get_field_ref<0>(x), so I can assign value to it.

struct A {
  int a;
  double b;
};

A a;
rfl::get_field_ref<0>(x) = 1;

I dug the code a little and find rfl::internal::to_ptr_named_tuple, but this can only get a const pointer

auto ptr_named_tuple = rfl::internal::to_ptr_named_tuple(homer);
ptr_named_tuple.get<0>(); // this is a const pointer.

So how to get an non-const reference/pointer?

Could anyone help me, thanks in advance!

liuzicheng1987 commented 8 months ago

@lixin-wei

Yes, I was thinking about this last week or so.

We don’t support something like rfl::get_field_ref, because it would be inefficient…you would have to extract all the fields just to get one.

However, what we can do is to offer a version of to_ptr_named_tuple that is non-const. I find this intriguing, because it would basically be like dict in Python only that it would be checked at compile time.

It wouldn’t be hard either. I will build something like that over the next couple of days and let you know.

If you have any good idea for what that function should be called, let me know.

lixin-wei commented 8 months ago

@liuzicheng1987 Thank you so much for the quick response.

I just made it using const_cast as a walk around like this:

template <size_t index, typename T>
constexpr auto& GetNthField(T& t) {
  auto ptr_named_tuple = rfl::internal::to_ptr_named_tuple(t);
  auto const_ptr = ptr_named_tuple.template get<index>();

  using const_type = std::remove_pointer_t<decltype(const_ptr)>;
  using type = std::remove_const_t<const_type>;

  return *const_cast<type*>(const_ptr);
}

It works so I'm not in hurry now, so take your time😊 Thanks again for you quick support.

I'm also willing to contribute one if you don't have time.

ChemiCalChems commented 8 months ago

rfl::internal::tuple_view (used currently for rfl::internal::bind_to_tuple) returns non-const references to fields that can be then used for whatever purpose.

lixin-wei commented 8 months ago

@ChemiCalChems Oh you just added it theses days right? It works perfectly, thanks a lot!

Could you please make it as public API?

liuzicheng1987 commented 8 months ago

@ChemiCalChems , the problem with using it this way is that you wouldn't have access to the names of the fields. So, yes, you could use rfl::internal::bind_to_tuple to write something like @lixin-wei 's GetNthField (and that would be better than const casts). But implementations like this have a serious performance problem. Because you have to create references to ALL of the fields just to get one.

Just consider something like this:

auto f1 = GetNthField<0>(my_struct); // has to get ALL the fields
auto f2 = GetNthField<1>(my_struct); // has to get ALL the fields again - very inefficient

*f1 = new_value1; 
*f2 = new_value2; 

Boost.PFR has the exact same problem - there is no cheap way around this.

I think the best solution would be to do something like Python's __dict__. In case you don't know, you can access any Python object like this:

my_obj.__dict__

This will give you a dictionary representation of the same object. So my solution would be something like this:

auto view = rfl::to_view(my_struct);

view would then be a named tuple, which would allow you to access fields by index or by name:

auto view = rfl::to_view(my_struct); // Gets all fields in one call -  much better

auto f1 = view.get<0>();
auto f2 = view.get<"name_of_other_field">();

// Modifies the original struct
*f1 = new_value1; 
*f2 = new_value2; 

I think this is the best trade-off between convenience and performance that I can think of.

liuzicheng1987 commented 8 months ago

@lixin-wei , if you want to give it a go yourself, the basic idea would be to rewrite rfl::internal::to_ptr_named_tuple such that it supports both const Ts and non-const Ts. (But no const_cast, please).

Then you could just slap a convenient function called rfl::to_view on top of that.

A PR is welcome. Otherwise I would do it myself over the next couple of days, when I find time.

ChemiCalChems commented 8 months ago

If this were to go ahead, I'd use rfl::internal::bind_to_tuple to get a tuple of references from rfl::internal::tuple_view and attach names to it if needed too.

liuzicheng1987 commented 8 months ago

What we are looking for is to_named_tuple, but with pointers.

https://github.com/getml/reflect-cpp/blob/main/include/rfl/to_named_tuple.hpp

lixin-wei commented 8 months ago

@liuzicheng1987 After thinking a while I find it seems too hard for me as the first PR😂 You can go ahead pelase, I'll watch your incomming code and learn from it first. Thanks for the opportunity.

liuzicheng1987 commented 8 months ago

@lixin-wei , OK, I will do it.

liuzicheng1987 commented 8 months ago

@lixin-wei , I have implemented the change as discussed and issued a new release (v0.4.0).