Last active
May 14, 2023 17:06
-
-
Save jharmer95/d6337ccd58f3b7470b65e858d509022a to your computer and use it in GitHub Desktop.
Class emulating properties for C++
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <memory> | |
#include <type_traits> | |
#include <utility> | |
namespace not_std | |
{ | |
template<typename T> | |
constexpr T default_getter(const T& val) noexcept | |
{ | |
return val; | |
} | |
template<typename T> | |
constexpr void default_setter(T& val, T&& new_val) noexcept | |
{ | |
val = std::forward<T>(new_val); | |
} | |
template<typename T> | |
class property_ptr; | |
template<typename T> | |
class const_property_ptr; | |
template<typename T> | |
class property | |
{ | |
public: | |
using getter_type = T(*)(const T&); | |
using setter_type = void(*)(T&, T&&); | |
using value_type = T; | |
property() = default; | |
explicit property(T init_val) noexcept(std::is_nothrow_move_assignable_v<T>) | |
: m_val(std::move(init_val)) | |
{ | |
} | |
explicit property(getter_type get_func, setter_type set_func, T init_val = T{}) | |
noexcept(std::is_nothrow_default_constructible_v<T> && std::is_nothrow_move_constructible_v<T>) | |
requires(std::is_default_constructible_v<T>) | |
: m_val(std::move(init_val)), | |
m_getter(get_func), | |
m_setter(set_func) | |
{ | |
} | |
explicit property(getter_type get_func, setter_type set_func, T init_val) | |
noexcept(std::is_nothrow_move_constructible_v<T>) | |
requires(!std::is_default_constructible_v<T>) | |
: m_val(std::move(init_val)), | |
m_getter(get_func), | |
m_setter(set_func) | |
{ | |
} | |
property_ptr<T> operator&() noexcept { return property_ptr<T>{ this }; } | |
const_property_ptr<T> operator&() const noexcept { return const_property_ptr<T>{ this }; } | |
operator T() const { return get(); } | |
property& operator=(T&& new_val) | |
{ | |
m_setter(m_val, std::forward<T>(new_val)); | |
return *this; | |
} | |
template<typename U> | |
property& operator+=(const U& op) | |
requires requires(T& t, const U& u) { t += u; } | |
{ | |
m_setter(m_val, m_val + op); | |
return *this; | |
} | |
template<typename U> | |
property& operator-=(const U& op) | |
requires requires(T& t, const U& u) { t -= u; } | |
{ | |
m_setter(m_val, m_val - op); | |
return *this; | |
} | |
template<typename U> | |
property& operator*=(const U& op) | |
requires requires(T& t, const U& u) { t *= u; } | |
{ | |
m_setter(m_val, m_val * op); | |
return *this; | |
} | |
template<typename U> | |
property& operator/=(const U& op) | |
requires requires(T& t, const U& u) { t /= u; } | |
{ | |
m_setter(m_val, m_val / op); | |
return *this; | |
} | |
template<typename U> | |
property& operator%=(const U& op) | |
requires requires(T& t, const U& u) { t %= u; } | |
{ | |
m_setter(m_val, m_val % op); | |
return *this; | |
} | |
template<typename U> | |
property& operator^=(const U& op) | |
requires requires(T& t, const U& u) { t ^= u; } | |
{ | |
m_setter(m_val, m_val ^ op); | |
return *this; | |
} | |
template<typename U> | |
property& operator&=(const U& op) | |
requires requires(T& t, const U& u) { t &= u; } | |
{ | |
m_setter(m_val, m_val & op); | |
return *this; | |
} | |
template<typename U> | |
property& operator|=(const U& op) | |
requires requires(T& t, const U& u) { t |= u; } | |
{ | |
m_setter(m_val, m_val | op); | |
return *this; | |
} | |
template<typename U> | |
property& operator>>=(const U& op) | |
requires requires(T& t, const U& u) { t >>= u; } | |
{ | |
m_setter(m_val, m_val >> op); | |
return *this; | |
} | |
template<typename U> | |
property& operator<<=(const U& op) | |
requires requires(T& t, const U& u) { t <<= u; } | |
{ | |
m_setter(m_val, m_val << op); | |
return *this; | |
} | |
property& operator++() | |
requires requires(T& t) { ++t; } | |
{ | |
auto cpy = m_val; | |
++cpy; | |
m_setter(m_val, std::move(cpy)); | |
return *this; | |
} | |
property operator++(int) | |
requires requires(T& t) { t++; } | |
{ | |
property old = *this; | |
operator++(); | |
return old; | |
} | |
property& operator--() | |
requires requires(T& t) { --t; } | |
{ | |
auto cpy = m_val; | |
--cpy; | |
m_setter(m_val, std::move(cpy)); | |
return *this; | |
} | |
property operator--(int) | |
requires requires(T& t) { t--; } | |
{ | |
property old = *this; | |
operator--(); | |
return old; | |
} | |
T get() const { return m_getter(m_val); } | |
void set(T&& new_val) { m_setter(m_val, std::forward<T>(new_val)); } | |
private: | |
friend class property_ptr<T>; | |
friend class const_property_ptr<T>; | |
T m_val; | |
getter_type m_getter{ default_getter<T> }; | |
setter_type m_setter{ default_setter<T> }; | |
}; | |
template<typename T> | |
class property_ptr | |
{ | |
public: | |
using pointer = property<T>*; | |
using element_type = T; | |
using value_pointer = const T*; | |
property_ptr(pointer ptr) : m_ptr(ptr) {} | |
property<T>& operator*() { return *m_ptr; } | |
const T& operator*() const { return m_ptr->m_val; } | |
pointer operator->() const noexcept { return get(); } | |
operator value_pointer() const { return &m_ptr->m_val; } | |
pointer get() const noexcept { return m_ptr; } | |
private: | |
pointer m_ptr; | |
}; | |
template<typename T> | |
class const_property_ptr | |
{ | |
public: | |
using pointer = const property<T>*; | |
using element_type = T; | |
using value_pointer = const T*; | |
const_property_ptr(pointer ptr) : m_ptr(ptr) {} | |
const T& operator*() const { return m_ptr->m_val; } | |
pointer operator->() const noexcept { return get(); } | |
operator value_pointer() const { return &m_ptr->m_val; } | |
pointer get() const noexcept { return m_ptr; } | |
private: | |
pointer m_ptr; | |
}; | |
template<typename T> | |
std::ostream& operator<<(std::ostream& os, const property<T>& p) | |
{ | |
os << p.get(); | |
return os; | |
} | |
} // namespace not_std |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "properties.hpp" | |
#include <cstdio> | |
#include <iostream> | |
struct X | |
{ | |
X(int n = 0) noexcept : num(n) {} | |
not_std::property<int> num; | |
}; | |
struct Y | |
{ | |
not_std::property<int> num { | |
// get | |
[](const int& val) | |
{ | |
std::cout << "GET: " << val + 1 << '\n'; | |
return val + 1; | |
}, | |
// set | |
[](int& val, int&& new_val) | |
{ | |
std::cout << "SET: " << val << " -> " << new_val << '\n'; | |
val = new_val; | |
}, | |
0 | |
}; | |
}; | |
struct Z | |
{ | |
not_std::property<std::string> str { | |
// get | |
[](const std::string& val) | |
{ | |
std::cout << "GET: " << val << '\n'; | |
return val; | |
}, | |
// set | |
[](std::string& val, std::string&& new_val) | |
{ | |
std::cout << "SET: " << val << " -> " << new_val << '\n'; | |
val = std::move(new_val); | |
} | |
}; | |
}; | |
void print_int(const int* p) | |
{ | |
std::cout << *p << '\n'; | |
} | |
int main() | |
{ | |
X x; | |
Y y; | |
Z z; | |
x.num = 12; | |
y.num.set(36); | |
y.num /= 2; | |
++y.num; | |
y.num += 7; | |
z.str = "Hello world"; | |
z.str += " and friends"; | |
std::cout << x.num << '\n'; | |
std::cout << y.num + 2 << '\n'; | |
std::cout << (y.num == 26 ? "True\n" : "False\n"); | |
std::cout << z.str << '\n'; | |
const not_std::property<int> cx{ 22 }; | |
const auto p = &cx; | |
p->get(); | |
std::cout << *p << '\n'; | |
print_int(p); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment