billyquith / ponder

C++ reflection library with Lua binding, and JSON and XML serialisation.
http://billyquith.github.io/ponder/
Other
640 stars 93 forks source link

Consistent behaviour regarding const objects #66

Open Weeena opened 7 years ago

Weeena commented 7 years ago

Consider a simple class with a getter and a setter

class MyClass
{
public:
    MyClass() : i_(0) {};
    int Getter() const { return i_; };
    MyClass& Setter(int i) { i_ = i; return *this; };
private:
    int i_;
};

that is declared to ponder

PONDER_TYPE(MyClass)

ponder::Class::declare<MyClass>("MyClass")
    .constructor()
    .property("Object", &MyClass::Getter, &MyClass::Setter);

If I have a const reference to an object of this class I am not allowed to modify it. With a const ponder::UserObject, on the other hand, modifying this object is possible:

const MyClass myClassObject;
const MyClass& refToMyClassObject = myClassObject;
refToMyClassObject.Setter(41); // COMPILATION ERROR! Not allowed to modify a const object
const ponder::UserObject myClassUserObject(refToMyClassObject);
myClassUserObject.set("Object", 42); // No compilation error, const object gets modified
assert(refToMyClassObject.Getter() == 42);

With all that const around that behaviour may be counterintuitive.

From my point of view, what possibilities would exist to accomplish a consistent behaviour:

1. Do not allow to make a ponder::UserObject from a const object

Reasoning: WYSIWYG (= what you see [in the interface] is what you get). This would mean that in ponder e.g.

    template <typename T>
    UserObject(const T& object);

would have to be changed to

    template <typename T>
    UserObject(T& object);

I guess this is not a very attractive option. If you like to reflect on a const object (e.g. to serialize it) and plan to only read its properties, you would have to apply a const_cast which is always something you like to avoid.

2. Allow a ponder::UserObject to be made from and to modify a const object consequently

Reasoning: Reflection is something that exceeds the normal possibilities of a language.

In this case, ponder's interface could essentially stay as it is now. To be consequent, though,

inline UserObject Class::getUserObjectFromPointer(void* ptr) const
{
    return m_userObjectCreator(ptr);
}

should in my opinion then be changed to

inline UserObject Class::getUserObjectFromPointer(const void* ptr) const
{
    return m_userObjectCreator(ptr);
}

Of course, it would also have to be emphasized in the documentation that with ponder's reflection you are able to modify const objects.

3. Have a ponder::UserObject support a read-write as well as a read-only mode

Reasoning: Once againg WYSIWYG. With this option, if you make a ponder::UserObject from some const object this would then be the read-only mode where you would not be allowed to modify this object.

I guess this would be the best option, yet associated with the most effort.


What do you think? Was ponder intentionally designed for option 2? In any case, option 2 is what ponder now supports. Hence I would be happy if you would approve the modification I suggest in option 2 (which would avoid the const_casts that I now need to reflect on objects for which I have a const void*.)

billyquith commented 7 years ago

It's a tough one and perhaps needs a different angle. I did think about refactoring UserObject and making it policy based. I.e. you would pass it the traits that you would like it to have, like class member declarations can currently.

A UserObject currently owns a data holder, so two objects are allocated. This might be collapsed into one for some purposes. I never really got past that stage though.

In the case of const, as you mention, because of language constraints, perhaps opaque handles need to be used and a similar API to the runtime calling.