rttrorg / rttr

C++ Reflection Library
https://www.rttr.org
MIT License
3.18k stars 441 forks source link

Setting the value of std::shared_ptr-like properties #60

Closed rovarma closed 7 years ago

rovarma commented 7 years ago

We have a custom pointer type that we use to do some registration of objects and such. We'd like to expose properties of this type through RTTR. We've got it mostly working, but we're stuck on actually being able to set the values of these properties. We've defined the wrapper_mapper (basically identical to the one for unique_ptr) for our type, so the code looks something like this (simplified):

template<class T>
class ObjectPtr
{
public:
    /** Functions for getting, setting, assigning and such **/
private:
    T* mPtr;
}

namespace rttr
{
    template<typename T>
    struct wrapper_mapper<ObjectPtr<T>>
    {
        using wrapped_type = decltype(std::declval<ObjectPtr<T>>().get());
        using type = ObjectPtr<T>;

        inline static wrapped_type get(const type& obj)
        {
            return obj.get();
        }

        inline static type create(const wrapped_type& value)
        {
            return ObjectPtr<T>(value);
        }       
    };
}

Now, given the following type of class, we'd like to be able to get/set the pointer value; it should be as transparent as possible to the user that this is actually a pointer wrapper type. Getting the value works, but settings is problematic:

class Foo
{
private:
    ObjectPtr<Bar> mPointer;
}

Foo* object = new Foo();
Bar* pointee = object->get_type().get_property("mPointer").get_value(object).extract_wrapped_value(); // Works
object->get_type().get_property("mPointer").set_value(object, ObjectPtr<Bar>(new Bar())); // Works
object->get_type().get_property("mPointer").set_value(object, new Bar()); // Doesn't work

So, it almost works, but we can't set the value of this property from a regular pointer (the last case). This is problematic for us, because during deserialization we want to set the values of these pointers (i.e. pointer fixup, like we do with regular pointers), but we only have naked pointers, so set_value fails. From our previous discussion in https://github.com/rttrorg/rttr/issues/56, I know that RTTR expects the value type to match exactly, and that we can use variant::convert to do this:

variant var = b;
auto ret = var.convert(prop.get_type()); // will convert internaly 'Base*' to 'Derived*'

Foo obj;
prop.set_value(obj, var); // will succeed

We're using this code to be able to set raw pointer types in a polymorphic fashion, which is great, but unfortunately the convert code does not deal with this case:

variant var = new Bar();
var.convert(object->get_type().get_property("mPointer").get_value(object).get_type()); // Fails

As an additional curiosity, the wrapper_mapper expects a create() function to be defined, which I had hoped would be used for these cases, but as far as I can tell that function is never called and I can't find a place in the codebase where it does. The get function on wrapper_mapper is correctly called as expected when getting the value from this proxy object.

Any ideas on how to approach/solve this problem?

acki-m commented 7 years ago

You have two options here: 1) Register a converter function in type::register_converter_func

type::register_converter_func(
[](Bar* ptr, bool& ok) -> ObjectPtr<Bar> 
{ 
    ok = true; 
    return ObjectPtr<Bar>(ptr); 
});

This conversion function will now used internally by the variant class, when you call convert Now you can convert from a ptr to your smart pointer; the vice versa function can also be added.

  1. I guess your ObjectPtr has also a setter and getter function? You can register them for this particular type too.
    
    registration::class_<ObjectPtr<Bar>>("ObjectPtr<Bar>")
    .method("setter", &ObjectPtr<Bar>::setter);

Foo* object = new Foo(); variant var = type::get(object).get_property("mPointer").get_value(object); method meth = var.get_type().get_method("setter"); meth.set_value(var, new Bar());



Will this help?

PS: I see that in the docu the link to the `register_converter_func` is missing. I will add this.
PPS: You are right, the `create()` function is not used at the moment, I added it for a future application.
Maybe we have here one?  :smile:
rovarma commented 7 years ago

I actually came across the converter function stuff, but I think that doesn't work in this case, because it assumes I know the type I want to convert to. In this case, I want to support all ObjectPtr<T> in a generic way, rather than a specific instantiation . If I understand correctly (and also from your example code), this is not supported, right?

The same goes for the getter/setter; having to register each and every instantiation of ObjectPtr is not really feasible.

The create thing sounds like a good candidate ;) Again, if you can give me some pointers, I'd be happy to help implement this.