Have you ever written proxy objects, instead of using a setter and a getter method? In that case, I'm interested in your opinion on the following design for a templated proxy.
This is a second version, after addressing comments from a previous review; it is intended to work with C++11 or newer. Issues which have been addressed + changes:
- Support for
const
proxies. - Move constructor.
- The getter possibly returning a reference, or some other funny type.
noexcept()
decorationconstexpr
decoration
#include <type_traits>
#include <utility>
#if __cplusplus >= 201402L
#define CONSTEXPR_SINCE_CPP14 constexpr
#else
#define CONSTEXPR_SINCE_CPP14
#endif
template <typename Handle, typename Getter, typename Setter>
class proxy {
public:
using getter_return_type = decltype(std::declval<Getter>()(std::declval<Handle>()) );
// Note: We assume the getter does not allow modifying the value. If it does - you don't
// need the proxy in the first place. But _asserting_ this is difficult. For example, suppose
// the getter returns an int*. Is the "actual" value an int, or an int*? We can't know that
// without introducing yet another type parameter.
using value_type = typename std::remove_reference<typename std::remove_cv<getter_return_type>::type>::type;
CONSTEXPR_SINCE_CPP14
operator getter_return_type() const noexcept(noexcept(getter_))
{ return getter_(handle_); }
CONSTEXPR_SINCE_CPP14 proxy&
operator=(const value_type& x) noexcept(noexcept(setter_))
{ setter_(handle_, x); return *this; }
CONSTEXPR_SINCE_CPP14 proxy&
operator=(value_type&& x) noexcept(noexcept(setter_))
{ setter_(handle_, std::move(x)); return *this; }
CONSTEXPR_SINCE_CPP14
proxy(Handle handle, const Getter& getter, const Setter& setter) noexcept
: handle_(handle), getter_(getter), setter_(setter) { }
protected:
const Handle handle_;
// Note: The handle is constant, not the referred-to element. So, _don't_ use a `T&` as your Handle, or
// you'll be in trouble
const Getter& getter_;
const Setter& setter_;
// Note: Attempts to use just plain Getter and Setter as the types here don't work when Getter/Setter
// are function types
};
// Allows for template argument deduction during "construction" - before C++17
template <typename Handle, typename Getter, typename Setter>
CONSTEXPR_SINCE_CPP14 proxy<Handle, Getter, Setter>
make_proxy(const Handle& handle, const Getter& getter, const Setter& setter) noexcept
{
return proxy<Handle, Getter, Setter>(handle, getter, setter);
}
And here are a couple of examples:
template <class T>
constexpr typename std::remove_const<T>::type& as_non_const(T& t) noexcept
{
return const_cast<typename std::remove_const<T>::type&>(t);
}
// This exists in C++14, but this example is compiled with C++11
template <class T>
constexpr typename std::add_const<T>::type& as_const(T& t) noexcept
{
return t;
}
const int& my_getter(int *x) { return *x; }
void my_setter(int *x, int val ) { *x = val; }
class foo {
public:
using proxy_type = proxy<int*, decltype(my_getter), decltype(my_setter)>;
proxy_type datum() { return make_proxy(&x, my_getter, my_setter); }
const proxy_type datum() const {
return make_proxy(&as_non_const(x), my_getter, my_setter);
}
protected:
int x { 123 };
};
int example1()
{
foo my_foo;
my_foo.datum() = 456;
return as_const(my_foo).datum();
}
double example2()
{
double x=1;
double coef=2.;
auto pr = make_proxy(&x,
[coef](double*x){return *x*coef;},
[coef](double*x, double nx){*x = nx/coef;});
pr = 8.;
return as_const(pr);
}
See it all on GodBolt.
Other than general comments, I'd appreciate help with:
- What do you think of my constexpr'ization? Is it reasonably executed? Should I even bother with it?
- Ditto regarding the use of
noexcept()
. - Can I, and should I, make the
getter_
andsetter_
members into possibly-non-reference types? - Is there any use to accepting the value type as an explicit template parameter?
- What if someone wants to use a
T&
as the handle? i.e. usex
as the handle in the examples rather than&x
, with appropriate getter and setter? - What are your thoughts on comparison operators?