Introduction
Even though we all know C# properties are a good replacement of writing getter/setter functions for every single class member field, virtually we don't have a standard way to use properties in C++. Here is a quick implementation of C++ alternative of properties. The design is intended to give a similar-most feeling that you'd have when using C# properties, although there are some nasty aspects in the implementation.
Using the Code
When using the code, just include 'prop.h' at the top of your code. ('prop.cpp' is an example source code.) The design mostly follows the philosophy of C# properties, but there are some minor syntactic details you need to follow.
Declaration
First, if your property doesn't need to implement any special get/set function, then don't.
class Test {
public:
prop<size_t> p; }
You can use the declared property just like a plain member field. For example:
int foo() {
Test test;
test.p = 3;
std::cout << test.p << std::endl;
}
Second, if your property needs to implement its own getter and setter, feed it the definitions like this. Note that the setter function receives the caller-given value through the reserved keyword, value
.
class Test {
size_t p_org;
public:
prop<size_t> p {
__get__() { return p_org; }
__set__(size_t) { p_org = value; }
};
};
Finally, you can also declare a read-only property by providing it only a getter function and specifying the original type as const
.
class Test {
size_t p_org;
public:
prop<const size_t> p_readonly {
__get__() { return p_org; }
};
};
If you try to set any value with a read-only property, you'd get the following compile-time error.
int main() {
Test test;
test.p_readonly = 0xdeadbeef; return 0;
}
...
test.cpp:3:19: error: no viable overloaded '='
test.p_readonly = 0xdeadbeef;
~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~
Some important points are below;
- You need to keep the order of the getter and the setter; you need to declare the getter first.
- Any property needs to be declared with the primitive types, such as integers or floating points.
(It assumes the original type has a proper definition of ++, --, and all kinds of compound assignment operators.) - Read-only properties must be declared with a
const
type, and it must be provided with a getter definition.
(Otherwise, you won't have a way to get a value you want to get anyway!)
Initialization
You can initialize a default property directly with the property's name at the constructor.
class Test {
public:
prop<size_t> p;
Test() : p(0) {}
};
But in case a property has its own getter/setter, you may initialize the original data member rather than the property itself.
class Test {
public:
size_t p_org;
prop<size_t> p {
__get__() { return p_org; }
__set__(size_t) { p_org = value; }
};
Test() : p_org(0) {}
};
Property Read/Write
Reading from and writing to a property is just like you'd do with regular public member fields. It supports all kinds of operations that can be used on the regular variables. Please refer to 'prop.cpp' in the attachment to see the example read/write usages.
How It Works
Below is the simplified source code of the header 'prop.h'.
#pragma once
#include <cassert>
#include <functional>
template <typename T>
class prop {
public:
using Getter = std::function<T()>;
using Setter = std::function<void(T)>;
private:
T __val__;
Getter __getter__;
Setter __setter__;
void initialize() {
__getter__ = [&]() { return __val__; };
__setter__ = [&](T value) { __val__ = value; };
}
public:
prop() { initialize(); }
prop(T o) : __val__(o) { initialize(); }
prop(Getter getter, Setter setter = readonly_setter()) :
__getter__(getter), __setter__(setter) {}
virtual prop<T>& operator=(T o)
{ __setter__(o); return *this; }
virtual operator T() const
{ return __getter__(); }
};
template <typename T>
class prop<const T> {
public:
using Getter = std::function<T()>;
private:
Getter __getter__;
prop(const prop<T>&) = delete;
prop(prop<T>&&) = delete;
public:
prop(Getter getter) : __getter__(getter) {}
virtual operator T() const
{ return __getter__(); }
};
#define __get__() [&]()
#define __set__(T) , [&](T& value)
Basically, you'd declare the instance of the base class 'prop
' and optionally feed it your own getter/setter. The prop
class has three members, one (__val__
) for the data variable for the default getter/setter and the other two lambda variables (__getter__
, __setter__
) for the variable manipulation.
What you'd use to specify your own getter/setter (say, __get__()
and __set__()
) are actually the helper functions written in macro. Specifically, the __get__()
macro is the lambda declaration of the getter function, and so is the __set__()
macro.
One nasty trick here is putting a comma (,
) at the front of the __set__()
macro definition, which is simply there to obey the syntax of uniform initialization. I just put a comma there because it would prevent the confusion where programmers need to put an extra comma after the getter definition, and also because it may allow the property declarations to look less awkward, even a little.
Finally, the base class of read-only properties is the partially-specialized prop
class (prop<const T>
). To be more specific, the read-only prop
class specializes any constant type and never defines value-modifying operators. Since it must be provided with a proper getter definition, there is no default constructor defined in this case.
History
- 2019.05.28: Just posted
- 2019.05.28: Basic operations (++, --, +=, -=, *=, /=) and read-only properties (allow no
__set__()
definition) - 2019.05.29: Minor compilation bug fixed
- 2019.05.30: Minor compilation/runtime bug fixed
- 2019.06.06: Compile-time read-only property feature.