A simple meta-accessor

Created 20th July, 2007 09:49 (UTC), last edited 25th June, 2018 01:57 (UTC)

Here is a very minimal class that can be used to wrap an attribute so that syntactically it will behave as if it has accessors.

template< typename T >
class Accessors {
private:
	T m_t;
public:
	Accessors() {}
	Accessors( const T &t ) : m_t( t ) {}

	const T &operator() () const { return m_t; }
	void operator() ( const T &t ) { m_t = t; }
};

This is the absolute simplest implementation that I can think of and should be a good starting point. Here's how it can be used.

struct SomeObject {
	Accessors< int > an_int;
	Accessors< std::list< int > > some_ints;
} an_object;

void f() {
	an_object.an_int( 3 );
	int i = an_object.an_int();
}

void g() {
	std::list< int > ints = an_object.some_ints();
	ints.push_back( 3 );
	an_object.some_ints( ints );
}

As we can see from SomeObject the Accessors class pretty neatly encapsulated the accessors just as we want, and in f() we can see it works very well with a POD (the int). It really does suck for the std::list<> though.

Closing the assignment loophole

Nasty as it is for larger data structures (those that are expensive to copy) I want to fix something else first. At the moment we could change f() to this:

void f() {
	an_object.an_int = 3;
	int i = an_object.an_int();
}

This is really nasty. The whole point of the class is to allow us to use the same syntax as we'd use for accessors without actually having to write them. This isn't doing that.

As the exploitation relies on a copy we could disallow copy on Accessors. The standard way to do this is to make both the copy constructor and the assignment operator private and not provide implementations for them:

template< typename T >
class Accessors {
private:
	T m_t;
public:
	Accessors() {}
	Accessors( const T &t ) : m_t( t ) {}

	const T &operator() () const { return m_t; }
	void operator() ( const T &t ) { m_t = t; }
private:
	Accessors( const Accessors & );
	Accessors &operator =( const Accessors & );
};

We now get a compile error within f(), but we've now also stopped SomeObject instances from being copyable so this is not a good solution. A better one is to make the Accessors constructor which takes a value explicit.

template< typename T >
class Accessors {
private:
	T m_t;
public:
	Accessors() {}
	explicit Accessors( const T &t ) : m_t( t ) {}

	const T &operator() () const { return m_t; }
	void operator() ( const T &t ) { m_t = t; }
};

We now get an error when compiling f(), but we've preserved the copy capabilities of SomeObject. It is possible to do this in f() though:

void f() {
	an_object.an_int = Accessors< int >( 3 );
	int i = an_object.an_int();
}

I'm not going to worry about that. The idea here is to help us write less code and if people want to do all of that typing I'll let them.

Dealing with expensive attributes

So let's take a look again at the list of ints. If I was writing the accessors by hand I'd write two getters and no setter like this:

	std::list< int > &some_ints();
	const std::list< int > &some_ints() const;

We should clearly do the same thing in Accessors, but include the setter because we want it for the int.

template< typename T >
class Accessors {
private:
	T m_t;
public:
	Accessors() {}
	explicit Accessors( const T &t ) : m_t( t ) {}

	T &operator() () { return m_t; }
	const T &operator() () const { return m_t; }
	void operator() ( const T &t ) { m_t = t; }
};

The problem is that we now have an odd hybrid accessor pattern. The implementation is alright, but not as good as we could have it with a bit more effort.

What we need to do is to have two implementations of Accessors, one for simple data types and one for complex data types:

template< typename T >
class Accessors_lvalue {
private:
	T m_t;
public:
	Accessors_lvalue() {}
	explicit Accessors_lvalue( const T &t ) : m_t( t ) {}

	T &operator() () { return m_t; }
	const T &operator() () const { return m_t; }
};

template< typename T >
class Accessors_rvalue {
private:
	T m_t;
public:
	Accessors_rvalue() {}
	explicit Accessors_rvalue( const T &t ) : m_t( t ) {}

	const T &operator() () const { return m_t; }
	void operator() ( const T &t ) { m_t = t; }
};

struct SomeObject {
	Accessors_rvalue< int > an_int;
	Accessors_lvalue< std::list< int > > some_ints;
} an_object;

That we have to use two names is annoying, but at least g() now looks much more sensible:

void g() {
	an_object.some_ints().push_back( 3 );
}

Specialisation to the rescue

What if we could tell the implementation what we wanted?

enum Accessor_type { rvalue, lvalue };

template< typename T, Accessor_type >
class Accessors;

It turns out that this is exactly what we can do. What we're going to use is something called partial template specialisation. We're going to fix the second template parameter, but still allow people using our class to specify the first.

We start off with the normal template pre-amble, but now we are going to be using the template class we declared in order to define two versions of it. This means we have to specify it in the same way that we do when we use it anywhere else. This gives us these two implementations:

template< typename T >
class Accessors< T, lvalue > {
private:
	T m_t;
public:
	Accessors< T, lvalue >() {}
	explicit Accessors< T, lvalue >( const T &t ) : m_t( t ) {}

	T &operator() () { return m_t; }
	const T &operator() () const { return m_t; }
};

template< typename T >
class Accessors< T, rvalue > {
private:
	T m_t;
public:
	Accessors< T, rvalue >() {}
	explicit Accessors< T, rvalue >( const T &t ) : m_t( t ) {}

	const T &operator() () const { return m_t; }
	void operator() ( const T &t ) { m_t = t; }
};

Take a think about why we have to refer to the Accessors class like this. We are using the class to define two alternative versions of it. Each of these alternatives now takes a single type as a template parameter and either lvalue or rvalue depending on which implementation we want to have.

Now when we define SomeObject we also choose which sort of accessor to use:

struct SomeObject {
	Accessors< int, rvalue > an_int;
	Accessors< std::list< int >, lvalue > some_ints;
} an_object;

This works much better. We now get the exact implementation that we want¹ [1If there are other accessor patterns that you have you can include then in the enumeration and add specialisations for them. You can go as crazy as you like, but too much choice might be too hard to keep track of and choose between.].

There is one final tweak we can make. Most of the time I'm going to want to use this class it's to wrap some simple data type, often whilst I first work on a problem and before I find the need to do anything more sophisticated. This means that I'm nearly always using the rvalue option² [2It might be possible to use some much more complex template programming to get the compiler to automatically choose the right one, but it isn't something I've felt the need to explore yet.]. It'd be great if we could set that to the default:

template< typename T, Accessor_type = rvalue >
class Accessors;

And of course yet again we can do exactly that. SomeObject becomes a little bit neater now.

struct SomeObject {
	Accessors< int > an_int;
	Accessors< std::list< int >, lvalue > some_ints;
} an_object;

Notice how the declaration for an_int looks like it did for our first version before we dealt with the special case of the std::list<>? I think that's pretty neat.

Final code

Here's the final implementation of the Accessors template for the two accessor patterns we identified:

enum Accessor_type { rvalue, lvalue };

template< typename T, Accessor_type = rvalue >
class Accessors;

template< typename T >
class Accessors< T, lvalue > {
private:
	T m_t;
public:
	Accessors< T, lvalue >() {}
	explicit Accessors< T, lvalue >( const T &t ) : m_t( t ) {}

	T &operator() () { return m_t; }
	const T &operator() () const { return m_t; }
};

template< typename T >
class Accessors< T, rvalue > {
private:
	T m_t;
public:
	Accessors< T, rvalue >() {}
	explicit Accessors< T, rvalue >( const T &t ) : m_t( t ) {}

	const T &operator() () const { return m_t; }
	void operator() ( const T &t ) { m_t = t; }
};

It isn't perfect, but it is going to save you a lot of typing and refactoring effort. And even better than the saved typing is the fact that the run-time cost on a release build will be exactly zero³ [3Assuming you're not using a completely brain-dead compiler anyway.].

Two final points.

  1. We cannot use this class if we want to have the getter and setter to have different access permissions. In order to do that we need something more complex.
  2. Even use within the class requires us to use the accessor syntax. This is probably a good thing. If you do replace this with something more complex then you can remove the extra indirection where needed rather than trying to work out all the places you should be adding it in.

And finally, for const members make the type inside Accessors const rather than the Accessors object. I'll leave it to you to work out reasons for this.

Discussion for this page