Skip to content

Instantly share code, notes, and snippets.

@jharmer95
Last active May 14, 2023 17:06
Show Gist options
  • Save jharmer95/d6337ccd58f3b7470b65e858d509022a to your computer and use it in GitHub Desktop.
Save jharmer95/d6337ccd58f3b7470b65e858d509022a to your computer and use it in GitHub Desktop.
Class emulating properties for C++
#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
#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