C# has properties that behave like class member variables and capture read and write operations. We can implement similar behavior in modern C++ in only 15 lines of code.
Introduction
Traditionally, variables have been used as a space for storing and reading values. With evolution of programming, it was discovered that being able to do something when we store a value into a variable or read from it is quite practical. Today, most modern languages support this via properties.
In C#, a property has two pseudo functions that implement reading and writing. They are called a getter and a setter. Inside the setter, you can use a special keyword called value
. It holds the desired value that we want to assign to the property.
This is how your average C# property looks like:
public class Person {
private int _age;
public int Age
{
get { return _age; }
set { _age = value; }
}
}
In this example, the getter simply returns the value of persons' age, stored in the private
member variable _age
. And the setter assigns the previously mentioned value
to a private
variable _age
.
We would like to re-create this in Modern C++.
The Solution
Mimic Native Type
An int
property class should behave like an int
. Hence, our first task is to make our property class behave like its type. This can be done with a combination of a template, an implicit cast and an assignment operator.
So let's define the template that roughly behaves like a native type.
template<typename T>
class property {
operator T() const { return };
property<T>& operator= (const T& value) { }
}
We can now use our C++ property like this:
property<int> i;
i=10; int j=i;
Pass Setter and Getter
In the previous chapter, we discovered how we can capture get
and set
operations by implementing an implicit cast and an assignment operator. Now let us enable passing a custom setter and the getter functions to our properties.
This is a job for lambda expressions. To be able to define properties directly in C++ header files, let's pass getter and setter functions to a property via the constructor:
#include <functional>
template<typename T>
class property {
public:
property(
std::function<void(T)> setter,
std::function<T()> getter) :
setter_(setter),getter_(getter) { }
operator T() const { return getter_(); }
property<T>& operator= (const T &value) { setter_(value); return *this; }
private:
std::function<void(T)> setter_;
std::function<T()> getter_;
};
With this addition, we're almost there. In fact, if we only need to capture storing and reading values from the property, we are already there.
To fully mimic the C# property, we still need to implement that special value
member though.
The Value
Let's add a private
member variable of template type T
to the property class and call it value_
. To access this value, let's use a really dirty trick that will raise some eyebrows. Let's create a function value()
that simply returns a reference to private member value_
. Why? Because this way, we can assign to that property member variable directly - bypassing getters and setters, just like we can assign to a private
class member variable in C#.
This is the final property class after adding a private
variable value_
of type T
and a function T& value()
that returns a reference to it.
#include <functional>
template<typename T>
class property {
public:
property(
std::function<void(T)> setter,
std::function<T()> getter) :
setter_(setter),getter_(getter) { }
operator T() const { return getter_(); }
property<T>& operator= (const T &value) { setter_(value); return *this; }
T& value() { return value_; }
private:
std::function<void(T)> setter_;
std::function<T()> getter_;
T value_;
};
And we're done!
Using Properties
We can define our sample property in C++ like this:
class person {
public:
property<int> age {
[this] (int x) { this->age.value()=x; }, [this] () -> int { return this->age.value(); } };
};
And use it as an integer in the rest of our code:
person p;
p.age=10; std::cout<<p.age;
Known Issues
To completely mimic underlying type, our property class could override and pass other operators like ++,--,+=. In addition, the class could be separated to base class without the value_
called property
, and a derived class with a value_
member called value_property
.
History
- 25th October, 2020: Initial version