Update: I discovered several bugs with the initial implementation, so I've updated this post with a more complete solution.
Properties as seen in Python and C# are a pretty nice language feature, they allow you to have the syntax of accessing a public member variable, yet have that access still go through a getter or setter function.
I have to say I'm not totally sold on RW properties (I feel that if you are intending to change the state of a class, you should be forced to make it clear by calling a setter function) but I like RO properties, they remove the need to call a function to just access a value, and still allow you to check pre/post conditions or log access.
C++ doesn't have properties, if you Google around there's a general consensus that they don't fit the language well. However, I got a bit tired of my game engine's API having an excessive number of function calls. Here's an example:
TextureID rtt = window().new_texture(false);
window().mesh(rect_mesh)->set_texture_on_material(0, rtt);
It would be far nicer to be able to write:
TextureID rtt = window->new_texture(false);
window->mesh(rect_mesh)->set_texture_on_material(0, rtt);
... and still have the control that a getter function provides. So I wrote a Property template class, it looks like this..
template<typename Container, typename T>
class Property {
public:
Property(Container* _this, T Container::* member):
this_(_this),
getter_([member](Container* self) -> T& {
return self->*member;
}) {
}
Property(Container* _this, T* Container::* member):
this_(_this),
getter_([member](Container* self) -> T& {
return *(self->*member);
}) {
}
Property(Container* _this, std::shared_ptr<T> Container::* member):
this_(_this),
getter_([member](Container* self) -> T& {
return *(self->*member);
}) {
}
Property(Container* _this, std::function<T& (Container*)> getter):
this_(_this),
getter_(getter) {
}
/*
* We can't allow copy construction, because 'this_' will never be initialized
*/
Property(const Property& rhs) = delete;
Property operator=(const Property& rhs) {
assert(this_); //Make sure that this_ was initialized
getter_ = rhs.getter_;
// Intentionally don't transfer 'this_'
}
inline operator T&() const { return getter_(this_); }
inline T* operator->() const { return &getter_(this_); }
private:
Container* this_ = nullptr;
std::function<T& (Container*)> getter_;
};
Container is the class you are adding the property to, T is the type of the property.
Thanks to C++11 allowing initialization of members in the body of the class, and the awesome syntax of lambdas, we can use the Property template like so:
Property<WindowBase, Watcher> watcher = {
this, [](const WindowBase* self) -> Watcher& {
if(!self->watcher_) {
throw LogicError("Watcher has not been initialized");
} else {
return *self->watcher_.get();
}
}
};
Or, if you just want to wrap a member variable:
Property<WindowBase, Console> console = { this, &WindowBase::console_ };
Unfortunately we can't override the '.' operator in C++ so we can't get the exact same syntax as C# or Python, but the -> operator is good enough (and as a lot of my engine's accessors return pointers, it actually made things more consistent).