6

I really like the idea of properties in C#, and as a little side project, I've been tinkering with the idea of implementing them in C++. I ran into this example https://stackoverflow.com/a/5924594/245869 which seems fairly nice, but I couldn't help but think that lambdas and non-static data member initialization may make it possible to use some very nice syntax with this idea. Here's my implementation:

#include <iostream>
#include <functional>

using namespace std;


template< typename T >
class property {

public:
    property(function<const T&(void)> getter, function<void(const T&)> setter)
        : getter_(getter),
          setter_(setter)
    {};

    operator const T&() {
        return getter_();
    };

    property<T>& operator=(const T& value) {
        setter_(value);
    }

private:
    function<const T&(void)> getter_;
    function<void(const T&)> setter_;

};


class Foobar {

public:
    property<int> num {
        [&]() { return num_; },
        [&](const int& value) { num_ = value; }
    };

private:
    int num_;

};


int main() {
    // This version works fine...
    int myNum;
    property<int> num = property<int>(
        [&]() { return myNum; },
        [&](const int& value) { myNum = value; }
    );
    num = 5;

    cout << num << endl;  // Outputs 5
    cout << myNum << endl;  // Outputs 5 again.

    // This is what I would like to see work, if the property
    // member of Foobar would compile...
    // Foobar foo;
    // foo.num = 5;

    // cout << foo.num << endl;

    return 0;
}

I can use my property class normally [see the example in main()], but MinGW with g++4.7 doesn't particularly care for my attempt at using the property as a data member:

\property.cpp: In lambda function:
\property.cpp:40:7: error: invalid use of non-static data member 'Foobar::num_'

So it seems the concept of my property implementation works, but it might be in vain because I can't access other data members from my lambda functions. I'm not sure how the standard defines what I'm trying to do here, am I completely out of luck, or am I just not doing something right here?

Community
  • 1
  • 1
Bret Kuhns
  • 4,034
  • 5
  • 31
  • 43
  • 3
    Unrelated: You probably want `getter_(std::move(getter)), setter_(std::move(setter))` in the consructor, to support move-only types, and avoid extraneous copies in general. – R. Martinho Fernandes Apr 26 '12 at 14:33
  • Question: apart from being amusing, I believe a simple `int&` would be more useful. What's the advantage of your solution over a simple reference ? (Which itself is useless...) – Matthieu M. Apr 26 '12 at 15:34
  • @R.MartinhoFernandes I completely agree. The `property<>` itself also needs to support copy/move semantics so that classes with properties can be copied/moved. I wanted to keep the implementation as minimal as possible until I could get the concept working. Thanks though! – Bret Kuhns Apr 26 '12 at 16:49
  • @MatthieuM. Properties provide the same benefits as get/set methods, but allow clients to use cleaner syntax. It can get ugly once all your classes have get/set methods, just ask any Java developer (ie, `obj1.setSomething(obj2.getSomething())` versus `obj1.something = obj2.something`). The getters/setters allow the object to perform some manipulations either on the data itself (translate) or perform behind the scenes operations when getting/setting. For example, with a data member to a large object, I can wait until it's getter is called to load it instead of forcing it at construction. – Bret Kuhns Apr 26 '12 at 16:54
  • @BretKuhns: I personally find abundance setters equates with "bundle of data". I would therefore certainly refrain from making them looking cooler... but this is getting subjective. – Matthieu M. Apr 26 '12 at 18:31

1 Answers1

3

Your property is a different object (instance of property<int>) from the containing object (instance of Foobar). As such, its member functions get passed a different this, not the one you'd need to access num_ -- so you can't do it that way. If the lambdas were defined in a non-static member function of Foobar, they would have captured that function's this argument and would have had access to the enclosing object's members (explicitly, as this->num_). But the lambdas are defined in the class, where the non-static data members don't actually exist. If the lambdas did have access to num_, which num_, of which instance of Foobar, would have been that?

The easiest solution that I see is for the property to store a pointer to the enclosing object. That way, it can freely access its non-static members. The downside is that the declaration is slightly more complex (you'd have to do property<int, Foobar> num) and you'd need to initialize the property by passing the this pointer. So you won't be able to do it in the class, it would have to be in the constructor's initialization list, hence negating the advantage of C++11's data member initialization.

At that point, this would be available to the lambdas to capture anyway (by value, not by reference!) so your code would actually work with minimal changes, if you moved the initialization of the property to Foobar's constructor(s):

Foobar::Foobar():
    num {
        [this]() { return this->num_; },
        [this](const int& value) { this->num_ = value; }
    }
{
}

Does anyone know whether this, as passed to whatever constructor happens to be invoked, is available for non-static member initialization in the class definition? I suspect it isn't, but if it were, the same construction would work inside the class definition.

cvoinescu
  • 846
  • 5
  • 7
  • I just wanted to add that a more general problem of which this is a particular case, namely access from an object to the enclosing object, without storing a pointer to the outer object in the inner one, has always been tricky in C++. It can be done, but it doesn't look nice and I'm sure the pointer magic involved breaks strict-aliasing rules... – cvoinescu Apr 26 '12 at 15:18
  • I had found out about an hour after asking this question that making the capture clause [this] fixes the access to the data members, I wasn't sure why it was necessary, but your answer clears that up for me. Thanks! It turns out you don't need to move this to the constructor's initializer list. Changing the capture clause with the code in place as I had it works perfectly. – Bret Kuhns Apr 26 '12 at 16:46
  • There's one looming problem left, though, which isn't quite related to my original question. Because of the reasons you explained, the lambdas aren't able to access the private data members of my `Foobar` class. I haven't quite figured out how to properly friend `property` from `Foobar` to make this work. Setting the data members to `public` compiles and runs perfectly, so I think friending is the solution. – Bret Kuhns Apr 26 '12 at 16:48
  • 1
    @Bret : It's not possible to friend a lambda because it's not possible to declare its type in this context. – ildjarn Apr 26 '12 at 17:16
  • @ildjarn I believe, though, according to cvoinescu's explanation, the context of the lambdas should be with the `property<>`. I won't be able to play with this until later, but I'm not familiar with friending a template class. But, perhaps you're correct and the lambdas themselves are the problem, which as you pointed out, will be a dead end. – Bret Kuhns Apr 26 '12 at 17:47
  • @Bret : Right, but the context needs to be with the containing class (`Foobar`) in order to friend it. I don't think this is achievable. :-[ – ildjarn Apr 26 '12 at 17:49