billyquith / ponder

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

Support references to pondered objects when calling functions? #29

Closed Weeena closed 8 years ago

Weeena commented 8 years ago

Consider that you have some class A holding some data

class ClassA
{
public:
    ClassA() : TestMember(0) {};

    int TestMember;
};

and a second class B that is able to modify the data in class A

class ClassB
{
public:
    void ModifyA(ClassA& obj) { obj.TestMember = 5; }
};

Then you could declare those classes to ponder like this:

PONDER_TYPE(ClassA)
PONDER_TYPE(ClassB)

void declare_Classes()
{
    ponder::Class::declare<ClassA>("ClassA")
        .constructor()
        .property("TestMember", &ClassA::TestMember);
    ponder::Class::declare<ClassB>("ClassB")
        .constructor()
        .function("ModifyA", &ClassB::ModifyA);
}

Now consider the following code where function ModifyA() is called on an object of class A with the intend to modify that object:

    ClassA objectA;
    const ponder::Class& metaClassB = ponder::classByName("ClassB");
    ponder::UserObject wrapperB = metaClassB.construct();
    const ponder::Function& functionB = metaClassB.function("ModifyA");

    // Try to modify data in object A --> this does not work
    functionB.call(wrapperB, objectA);

    metaClassB.destroy(wrapperB);

This code does not work because the object that is passed to function ModifyA() is not a reference to objectA, rather it is a reference to a temporary copy of objectA.

What is your opinion? Should it be possible to pass references to objects (that have been declared to ponder) to functions?

(A workaround for the above problem would be to change ModifyA to

void ModifyA(const ponder::UserObject& obj)
{
    ClassA& objectA = obj.get<ClassA&>();
    objectA.TestMember = 5;
}

and pass UserObject(objectA) to ModifyA, but I don't consider this to be nice.)

billyquith commented 8 years ago

Looks like this is due to the ValueMapping. I'm wondering if this should be an optional feature or only necessary for users that require it. This could be quite a big change, to allow mutable non-const references.

billyquith commented 8 years ago

So this can be fixed by using a reference:

class ClassA
{
public:
    ClassA() : TestMember(0) {}
    int TestMember;
};

class ClassB
{
public:
    void ModifyA(ClassA* obj) { obj->TestMember = 5; }
};

PONDER_TYPE(ClassA)
PONDER_TYPE(ClassB)

void declare_Classes()
{
    ponder::Class::declare<ClassA>("ClassA")
        .constructor()
        .property("TestMember", &ClassA::TestMember);
    ponder::Class::declare<ClassB>("ClassB")
        .constructor()
        .function("ModifyA", &ClassB::ModifyA);
}

void test()
{
    ClassA objectA;
    const ponder::Class& metaClassB = ponder::classByName("ClassB");
    ponder::UserObject wrapperB = metaClassB.construct();
    const ponder::Function& functionB = metaClassB.function("ModifyA");

    functionB.call(wrapperB, &objectA);

    metaClassB.destroy(wrapperB);
}

This is quite subtle in Ponder (from CAMP) the way UserObjects work. Only pointers are seen as references.

I initially thought the reason was more complicated, to do with the Value mapping. The call helper could be made more efficient by using direct calling. Coercion is a convenience but ought to be optional.

billyquith commented 8 years ago

I'll close this as this appears to work.