5
\$\begingroup\$

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;
}
200_success
146k22 gold badges190 silver badges479 bronze badges
asked Aug 16, 2019 at 13:07
\$\endgroup\$
1
  • \$\begingroup\$ Why do you want to disable auto? It’s generally recommend to prefer type inference these days? \$\endgroup\$ Commented Aug 17, 2019 at 12:29

1 Answer 1

5
\$\begingroup\$

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.

answered Aug 16, 2019 at 23:37
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.