-
-
Save berdon/98f8c5bc631afa9542b2705617812d4f to your computer and use it in GitHub Desktop.
#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 |
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.)
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 :/.
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?
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.
Thanks a lot!
See https://austinhanson.com/properties-in-c-plus-plus-20/