The C++ logo, by Jeremy Kratz, licensed under CC0 1.0 Universal Public Domain Dedication

How to envelop a class with suffixes and prefixes and discover properties fields in C++

Notes published the
6 - 8 minutes to read, 1539 words

Enveloping a class

I found myself in the following situation.

There is a class foo, with a lot of member functions.

In more than one case, before any of the operations, another function needed to be called, and after any operation, another function needed to be called.

Most usages looked thus like

void bar(){
	call_before_using_foo();
	foo.doit();
	foo.doit2();
}

The code works but is very brittle as there are a lot of repetitions.

A possible approach would have been to create a wrapper class for foo, let’s call it foo2, and wrap every function call.

It’s a lot of boilerplate code, even if it has the advantage of providing a more clean and easy to use interface.

Fortunately, after experimenting for some time, I came up with a more generic solution, that I probably cannot use in this specific use case, but that provides a useful tool for other situations.

operator->

operator-> has the interesting property that it can hide easily function.

Let’s take for example std::unique_ptr. It defines operator-> to return the internal pointer.

Thus in the following snippet

struct Foo{
	void doit(){}
};
auto foo = std::make_unique<Foo>();
foo->doit();

foo->doit() calls operator-> of std::unique_ptr, it returns a pointer, and then calls the member function doit().

The code is equivalent to:

struct Foo{
	void doit(){}
};
auto foo = std::make_unique<Foo>();
Foo* f = foo.get();
f->doit();

A prefix class

So, by overloading operator-> (or operator*), it is possible to "hide" a function call.

It is trivial to create a class that calls another function when any member function of the internal class is called

template <class T>
struct prefix {
	T obj;
	template<typename... Args>
	explicit prefix(Args&& ... args) :
		obj(std::forward<Args>(args)..) {}

	T* operator->() {
		std::puts("prefix");
		return &obj;
	}
	const T* operator->() const {
		std::puts("prefix");
		return &obj;
	}
};

The class prefix can be used in the following way:

struct X {
	void doit(){
		std::puts("doit");
	}
};

void foo(){
	prefix<X> x;
	x->doit();
}

This should print to the console "prefix" and then "doit".

The wrap class; prefix and suffix hooks

It is a little more difficult to provide a suffix function, as it should get executed after operator-> returns.

As in other situations, adding another layer of indirection, in this case, a proxy type can solve the problem.

template <class T>
struct proxy_wrap {
	T& obj;
	proxy_wrap(T& obj_) : obj(obj_) {}
	~proxy_wrap() {
		std::puts("suffix");
	}
	T* operator->() {
		return &obj;
	}
};

template <class T>
struct wrap {
	T obj;
	template<typename... Args>
	explicit wrap(Args&& ... args) :
		obj(std::forward<Args>(args)...) {}

	proxy_wrap<T> operator->() {
		std::puts("prefix");
		return proxy_wrap<S,T>(obj, static_cast<S&>(*this));
	}
	proxy_wrap<const T> operator->() const {
		std::puts("prefix");
		return proxy_wrap<const T>(obj);
	}
};

struct X {
	void doit() const {
		std::puts("doit");
	}
};

int main(){
	wrapX x;
	x->doit();
}

This will print "prefix", then "doit", and at last "suffix".

A generic wrap class, with a custom prefix and suffix

So far, so good, but wrap is hardly reusable since our prefix and suffix actions are hardcoded.

We can pass as a separate template parameter what we want to execute. CRTP seems like a good solution to apply here.

template <class S, class T>
struct proxy_wrap {
	T& obj;
	S& pre;
	proxy_wrap(T& obj_, S& pre_) : obj(obj_) , pre(pre_){}
	~proxy_wrap() {
		pre.suffix(obj);
	}
	T* operator->() {
		return &obj;
	}
};

template <class S, class T>
struct wrap {
	T obj;
	template<typename... Args>
	explicit wrap(Args&& ... args) :
		obj(std::forward<Args>(args)...) {}

	proxy_wrap<S, T> operator->() {
		static_cast<S&>(*this).prefix(obj);
		return proxy_wrap<S,T>(obj, static_cast<S&>(*this));
	}
	proxy_wrap<const S, const T> operator->() const {
		static_cast<const S&>(*this).prefix(obj);
		return proxy_wrap<const S, const T>(obj, static_cast<const S&>(*this));
	}
};

At this point, we can create a custom wrapper for our specific need, that just needs to provide suffix and prefix as member functions:

struct X {
	int i = 0;
	int doit() {
		++i;
		std::cout << "doit " << i << '\n';
		return i;
	}
	int doit() const {
		std::cout << "doit " << i << '\n';
		return i;
	}
};

struct wrapX : wrap<wrapX, X> {
	void prefix(X& x) {
		++x.i;
		std::cout << "prefix " << x.i << '\n';
	}
	void prefix(const X& x) const {
		std::cout << "prefix " << x.i << '\n';
	}
	void suffix(X& x) {
		++x.i;
		std::cout << "suffix " << x.i << '\n';
	}
	void suffix(const X& x) const {
		std::cout << "suffix " << x.i << '\n';
	}
};

and use it similarly to before

void foo() {
	wrapX x;
	int res = x->doit();
}

It should print, "prefix 1", "doit 2" and "suffix 3".

The complete example can be found at compiler explorer.

Side note: performance considerations:

In simple examples, GCC already with -O1 was able to optimize everything away. Clang, with -O2, was also able to remove everything that was not necessary. MSVC, on the other hand, was not that good. It seems to remove all unnecessary calls but did not remove all the generated dead code, I should probably investigate further, maybe I’m missing some compiler flag.

Properties in C++

While testing, I tried to read directly the value of the member variable i, so I wrote

void foo() {
	wrapX x;
	int res = x->doit();
	int value = x->i;
}

To my surprise, the value of value is not 3, but 5, and in the console, two additional lines appeared: "prefix 4" and "suffix 5".

Of course operator-> is also called when accessing member variables, not only when calling member functions.

Thus without realizing it, the wrap does not only permit to add prefixes and suffixes to every function call but also to implement properties in C++.

Properties are a feature present in other languages, like Pascal or C# 🗄️ or VB.NET.

The main use case I’ve encountered with them are with GUIS, as they make the code much easier to read and write.

In those languages, it is possible to write something like

procedure TForm1.Button1Click(Sender: TObject);
begin
	Button1.Caption:='Button was clicked '; // Changes the caption of the GUI in real-time
end;

instead of explicitly calling getter and setter functions.

It’s syntactic sugar, but so are lambdas, operator->, operator++, and many other things that give us the possibility to design more powerful interfaces.

wrap can be used the same way, for the sake of brevity, instead of developing a whole GUI, let’s just change the terminal output when the button is "clicked".

struct button {
	std::string caption;
};

struct wrap_button : wrap<wrap_button, button> {
	void prefix(const button&) const {
	}
	void suffix(const button& b) const {
		std::cout << b.caption << '\n';
	}
};

void obButtonClick(wrap_button& b){
	b->caption = "Button was clicked!";
}

int main(){
	wrap_button b;
	obButtonClick(b);
}

In a similar way, the suffix can be used to read values, for example, from an input field.

#include <string>

struct input_field {
	std::string content;
	input_field(std::string content_) : content(std::move(content_)){}
};

struct wrap_input_field : wrap<wrap_input_field, input_field> {
	using wrap::wrap;
	void prefix(const input_field& f) const {
		std::cout << "query text from gui: " << f.content << "length: " <<  f.content.size() << '\n';
	}
	void suffix(const input_field& f) const {
		std::cout << "write text to gui: "  << f.content << "length: " <<  f.content.size() << '\n';
	}
};

void rtrim(std::string& s){
	s.erase(s.find_last_not_of(" \n\r\t")+1);
}

void rtrim_input_field(wrap_input_field& b){
	rtrim(b->content);
}

int main(){
	wrap_input_field ff("Hello World!  ");
	rtrim_input_field(ff);
}

The downside of those properties is that as they are so hidden, that perfectly valid-looking code might be troublesome. For example, the following snippet queries the text twice and then writes it twice.

void rtrim_input_field(wrap_input_field& b){
	b->content.erase(b->content.find_last_not_of(" \n\r\t")+1);
}

In this case, as the operation is idempotent, it is "only" a performance issue, but generally, it can have much bigger and more problematic side effects.


Do you want to share your opinion? Or is there an error, some parts that are not clear enough?

You can contact me anytime.