Properties in C++20
C++ doesn't support getter/setter style properties. But you can get pretty darn close with templates or macros.
#include <iostream>
#include "properties.h"
using namespace std;
class User
{
private:
int mId;
int mWeight;
int mPurse;
int mBank;
public:
// Non-auto property only supporting get (runtime check)
Property<int> Id{{ .get = mId }};
Property<int> Purse {{ .set = mPurse }};
Property<int> Bank {{ .set = mBank }};
Property<int> Wealth {{ .get = [this]() { return mPurse + mBank; }}};
// Auto-properties
Property<string> FirstName;
Property<string> LastName;
// Non-auto property
Property<string> FullName {{
.get = [this]() { return LastName + string(", ") + FirstName; }
}};
// Property backed by member variables
Property<int> Weight { mWeight };
// Non-auto property with value checks
Property<int> Age {{
.get = [](int value) { return value; },
.set = [](int& value, int newValue) {
if (newValue < 0) newValue = 0;
if (newValue > 150) newValue = 150;
return value = newValue;
}
}};
User(int id) { mId = id; }
};
int main()
{
auto user = User(0);
cout << "First Name: ";
cin >> user.FirstName;
cout << "Last Name: ";
cin >> user.LastName;
user.Age = 46;
cout << "Full Name: " << user.FullName << endl;
user.Age++;
cout << "Age: " << user.Age << endl;
// user.Id = 10; // Throws an exception
user.Weight = 205;
user.Purse = 20;
user.Bank = 100;
cout << "Wealth: " << user.Wealth;
// user.Wealth = 100; // Throws an exception
}
It's not perfect but it's close.
We can get the above by extending Peter Dimov's Named Parameters in C++20 post and the templating approach for C++ properties on Wikipedia:
#include <iostream>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wreturn-stack-address"
template <typename T>
class Property
{
T _value;
bool _isAuto = true;
/**
* Used for auto-property behavior.
*/
std::function<const T&(const T&)> _autoGetter = [](const T& value) -> const T& { return value; };
std::function<const T&(T&, const T&)> _autoSetter = [](T& value, const T& newValue) -> const T& { return value = newValue; };
/**
* Used for non-auto property behavior.
*/
std::function<const T&()> _getter;
std::function<const T&(const T&)> _setter;
/**
* Convenience methods for calling the actual setter/getters regardless of auto-property
* or not.
*/
const T& set(const T& value) { return _isAuto ? _autoSetter(_value, value) : _setter(value); }
const T& get() { return _isAuto ? _autoGetter(_value) : _getter(); }
public:
struct AutoParams {
std::function<const T&(const T&)> get = [](const T& value) { return value; };
std::function<const T&(T&, const T&)> set = [](T& value, const T& newValue) { return value = newValue; };
};
struct Params {
std::function<const T&()> get = []() -> const T& { throw new std::exception(); };
std::function<const T&(const T&)> set = [](const T& value) -> const T& { throw new std::exception(); };
};
struct WrappedGetParams {
T& get;
};
struct WrappedSetParams {
T& set;
};
// Implicit conversion back to T.
operator const T& () { return get(); }
const T operator=(T other) { return set(other); }
const T operator=(Property<T> other) { return set(other.get()); }
Property<T>& operator++() { return set(get()++); }
T operator++(int n) {
return set(get() + (n != 0 ? n : 1));
}
Property<T>& operator--() { return set(get()--); }
T operator--(int n) {
return set(get() - (n != 0 ? n : 1));
}
const T& operator+=(const T& other) { return set(get() + other); }
const T& operator-=(const T& other) { return set(get() - other); }
const T& operator+(const T& other) { return get() + other; }
friend const T& operator+(const T& first, Property<T>& other) { return first + other.get(); }
const T& operator-(const T& other) { return get() - other; }
friend const T& operator-(const T& first, Property<T>& other) { return first - other.get(); }
const T& operator*(const T& other) { return get() * other; }
friend const T& operator*(const T& first, Property<T>& other) { return first * other.get(); }
const T& operator/(const T& other) { return get() / other; }
friend const T& operator/(const T& first, Property<T>& other) { return first / other.get(); }
friend std::ostream& operator<<(std::ostream& os, Property<T>& other) { return os << other.get(); }
friend std::istream& operator>>(std::istream& os, Property<T>& other) {
if (other._isAuto) {
return os >> other._value;
}
else {
T ref;
os >> ref;
other.set(ref);
return os;
}
}
// This template class member function template serves the purpose to make
// typing more strict. Assignment to this is only possible with exact identical types.
// The reason why it will cause an error is temporary variable created while implicit type conversion in reference initialization.
template <typename T2> T2& operator=(const T2& other)
{
T2& guard = _value;
throw guard; // Never reached.
}
Property() {}
Property(T& value)
{
_isAuto = false;
_getter = [&]() -> const T& { return value; };
_setter = [&](const T& newValue) -> const T& { return value = newValue; };
}
Property(AutoParams params)
{
_isAuto = true;
_autoGetter = params.get;
_autoSetter = params.set;
}
Property(Params params)
{
_isAuto = false;
_getter = params.get;
_setter = params.set;
}
Property(WrappedGetParams params)
{
_isAuto = false;
auto get = params.get;
_getter = [get]() { return get; };
_setter = [](const T& newValue) -> const T& { throw new std::exception(); };
}
Property(WrappedSetParams params)
{
_isAuto = false;
T& set = params.set;
_getter = []() -> const T& { throw new std::exception(); };
_setter = [&set](const T& newValue) { return set = newValue; };
}
};
#pragma GCC diagnostic pop