Skip to content

Instantly share code, notes, and snippets.

@berdon
Last active November 18, 2023 17:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save berdon/98f8c5bc631afa9542b2705617812d4f to your computer and use it in GitHub Desktop.
Save berdon/98f8c5bc631afa9542b2705617812d4f to your computer and use it in GitHub Desktop.
C++20 properties definition file that enables getter/setter style properties.
#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
@berdon
Copy link
Author

berdon commented Mar 5, 2021

@xparq
Copy link

xparq commented Oct 7, 2023

Hey, nice stuff!

Albeit, for just a copy-pasted smoke-testing (no time for more now, unfort.) I'm getting a bad_alloc exception with clang++ -std=c++20, and silent memory misreferencing (null-ref?) for a garbled FullName output (and maybe other similar scary stuff) with just plain vanilla clag++ ... (No warnings with -Wall. Version 16.)

UPDATE: Wait, it doesn't even compile with g++ (13.2), though! :-o

$ g++ -std=c++20 props-test.cpp
...
props.hpp:10:32: note: did you mean ‘-Wreturn-local-addr’?
props-test.cpp:18:70: error: could not convert ‘{{<lambda closure object>User::<lambda()>{((User*)this)}}
}’ from ‘<brace-enclosed initializer list>’ to ‘Property<int>’
   18 |   Property<int> Wealth {{ .get = [this]() { return mPurse + mBank; }}};
      |                                                                      ^
      |                                                                      |
      |                                                                      <brace-enclosed initializer
list>
props-test.cpp:27:4: error: could not convert ‘{{<lambda closure object>User::<lambda()>{((User*)this)}}
’ from ‘<brace-enclosed initializer list>’ to ‘Property<std::__cxx11::basic_string<char> >’
   27 |   }};
      |    ^
      |    |
      |    <brace-enclosed initializer list>
props-test.cpp:40:4: error: could not convert ‘{{<lambda closure object>User::<lambda(int)>(), <lambda cl
osure object>User::<lambda(int&, int)>()}}’ from ‘<brace-enclosed initializer list>’ to ‘Property<int>’
   40 |   }};
      |    ^
      |    |
      |    <brace-enclosed initializer list>
props.hpp: In instantiation of ‘Property<T>::Property(WrappedGetParams) [with T = int]’:
props-test.cpp:14:34:   required from here
props.hpp:135:13: error: no match for ‘operator=’ (operand types are ‘std::function<const int&()>’ and ‘P
roperty<int>::Property(WrappedGetParams)::<lambda()>’)
  135 |     _getter = [get]() { return get; };
      |     ~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/c++/13/functional:59,
                 from props.hpp:6:
/usr/include/c++/13/bits/std_function.h:531:9: note: candidate: ‘template<class _Functor> std::function<_
Res(_ArgTypes ...)>::_Requires<std::function<_Res(_ArgTypes ...)>::_Callable<_Functor>, std::function<_Re
s(_ArgTypes ...)>&> std::function<_Res(_ArgTypes ...)>::operator=(_Functor&&) [with _Res = const int&; _A
rgTypes = {}]’
  531 |         operator=(_Functor&& __f)
      |         ^~~~~~~~
...

(FWIW, I'm surprised that clang and gcc have such strong disagreement here. I only use them occasionally, but this divergence still feels unusual.)

@berdon
Copy link
Author

berdon commented Oct 8, 2023

Hey, nice stuff!

Albeit, for just a copy-pasted smoke-testing…

I’ll have to see what I can wrangle up from a couple years ago. I ended up playing around with this after working on some arduino stuff which is about all I can remember from the toolchain I was using. I was maybe using clang :/.

@rpolitex
Copy link

rpolitex commented Nov 18, 2023

Hi, @berdon
Nice idea for Properties!

Do you have any License restrictions for other developers to use this code as is or make derivatives from it?

@berdon
Copy link
Author

berdon commented Nov 18, 2023

Hi, @berdon Nice idea for Properties!

Do you have any License restrictions for other developers to use this code as is or make derivatives from it?

See https://austinhanson.com/properties-in-c-plus-plus-20/ for the few places it was derived from.

I attest no license for it. Use at will.

@rpolitex
Copy link

Thanks a lot!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment