I am trying to achieve a getter/setter behaviour that will reduce code in C++. The interface I would like to achieve is for example:
MyClass a;
a.velocity = 15; // Will dispatch a member function of MyClass responsible for setting "velocity".
someFloatFunc( a.velocity ); // Will implicitly convert to the argument type of someFloatFunc if necessary
I have come up with the following. Suggestions, comments, gotchas, are welcomed.
#include <iostream>
#include <functional>
#include <cassert>
using namespace std;
template <class T>
class encapsulate
{
public:
typedef T value_type;
typedef std::function<void( const T& )> setter_type;
// Disable things like auto b = a.v;. Access value like: int b = a.v;
encapsulate( const encapsulate& )=delete;
encapsulate( T&in, setter_type fin ):
v( in ),
f(fin)
{
}
encapsulate &operator= ( const T&o )
{
f( o );
return *this;
}
operator T() const { return v; }
private:
T &v;
setter_type f;
};
#define GETSET( TYPE, VARPUBLIC, VARPRIVATE, CLASS, SETTER ) \
encapsulate<TYPE> VARPUBLIC {VARPRIVATE, std::bind(&CLASS::SETTER,this,std::placeholders::_1 ) };\
private: \
TYPE VARPRIVATE; \
public:
#define GETSETDEFAULT( TYPE, VARPUBLIC, VARPRIVATE, CLASS, SETTER, DEF ) \
encapsulate<TYPE> VARPUBLIC {VARPRIVATE, std::bind(&CLASS::SETTER,this,std::placeholders::_1 ) };\
private: \
TYPE VARPRIVATE = DEF; \
public:
template <class T>
class A
{
public:
GETSET( T, v, v_, A, setValue )
void setValue( const T &other )
{
cout << "Setting value..." << endl;
v_ = other;
}
GETSETDEFAULT( double, g, g_, A, setGrayLevel, 0 )
void setGrayLevel( const double &gl )
{
if ( gl < 0 )
{
g_ = 0;
return;
}
if ( gl > 1 )
{
g_ = 1;
return;
}
g_ = gl;
}
};
const double testval = 215.33;
//Implicit conversion test
void printInt( const int &c )
{
std::cout << "PrintInt: " << c << std::endl;
assert( c == int(testval));
}
int main()
{
A<double> a;
a.v = testval;
std::cout << "a.v: " << a.v << std::endl;
//auto b = a.v; // Disallowed
double b = a.v;
assert( a.v == b);
b= testval-22;
assert( a.v != b);
printInt( a.v );
return 0;
}
1 Answer 1
A big issue with your solution is that you have to repeat yourself a lot, even when you have a macro to help you. I've come up with an alternative that doesn't use macros at all, and reduces the amount of repetition, although it still is more verbose that I would like. It uses the following class, which is similar to your class encapsulate
, except that it doesn't store a pointer to a setter function, but instead has virtual getter and setter functions that can be overridden in a derived class:
template<typename T>
class property
{
protected:
T v;
virtual const T &get(void) const {
return v;
}
virtual void set(const T &o) {
v = o;
}
public:
property() {}
property(const T &v_) { set(v_); }
property &operator=(const int &o) { set(o); return *this; }
operator T() const { return get(); }
};
So now you can declare and use a property variable this way:
property<int> foo;
foo = 42;
When you assign a value to foo
, it will call operator=()
, which in turn will call the function set()
. The trick is that since this is a virtual function, you can create a class or struct that inherits property<int>
, and override the set()
function. You can make use of the fact that you can declare a class or struct within another class or struct, and that you don't have to give them a name. It looks a bit weird at first, but it looks like this:
struct: property<int> { // declare a nameless struct that inherits from property<>
void set(const int &o) { // override the set() function
...;
}
} bar; // declare a variable named foo with this type
The main problem is that while a bare property variable works perfectly fine:
property<int> foo;
foo = 42;
The moment you inherit from it, the derived class has an implicitly defined assignment operator which hides the one in the base class. To get the one from the base class back, you have to add using property::operator=;
to the body of the derived class. The same goes for the constructor that takes a value.
Here is your class A
converted to use class property
:
template<typename T>
class A
{
public:
struct: property<T> {
using property::operator=; // required for assigning
void set(const T &other) {
std::cout << "Setting value..." << endl;
property::set(other);
}
} v;
struct: property<double> {
using property::operator=; // required for assigning
using property::property; // required for initialization to explicit value
void set(const T &gl) {
if (gl < 0)
property::set(0);
else if (gl > 1)
property::set(1);
else
property::set(gl);
}
} g = 0;
};
Another option that avoids having to add the using
statements is to make the derived property struct hidden, and add a base property reference to it that is public, like so:
class example {
struct: property<int> {
void set(const int &o) { ... }
} foo_;
public:
property<int> &foo = foo_;
};
When assigning a value to foo
, it doesn't know it's actually a derived class, so it will directly call operator=
from the base class, which is what we want.
Perhaps the above class property
can be combined with a macro that hides the verbosity. For example:
#define PROPERTY(type, setter) \
struct: property<type> { \
using property::property; \
using property::operator=; \
void set(const T &value) { property::set(setter(value)); } \
}
This way, you can provide the name of a function that acts as the setter, or even a lambda. For example:
class B {
static float invert(float x) {
return -x;
}
public:
PROPERTY(float, invert) foo;
PROPERTY(int, [](int v){ return v * v; }) bar = 0;
};
The drawback of this macro is that you can't easily use non-static member functions as getters and setters.
auto
? It’s generally recommend to prefer type inference these days? \$\endgroup\$