Implementation of Delegates in C++11

Home / Implementation of Delegates in C++11

Implementation of Delegates in C++11

December 5, 2015 | Article | 1 Comment

Introduction

In C++, it would be useful to be able to bind an object with its member function and produce a global function. Such features exists in other languages, for example C#. In C++, there are member function pointers but they do not provide the feature we talk about. In this article we will discuss about a simple implementation of delegates in C++ using member function pointers and C++11 using variadic templates.

For this article, I use:

  1. Slackware64 14.0 with gcc 4.7.1
  2. Windows 8 with Visual Studio 2010 express

Background

The approach is to provide a function create_delegate, which can be called in one of the following ways:

  • create_delegate(&object, &member_function)
  • create_delegate(&function)

The first option creates an object with a member operator() that can be called as a function. The second produces a function pointer, which is the same as &function. Both these values are compatible with type type function<...>.

A Sample Program

Let’s define a class with several methods

class A {
	int i;
public:
	A(int k):i(k) {}

	auto get()const ->int { return i; }
	auto set(int v)->void { i = v; }

	auto inc(int g)->int& { i+=g; return i; }
	auto incp(int& g)->int& { g+=i; return g; }

	auto f5 (int a1, int a2, int a3, int a4, int a5)const ->int {
		return i+a1+a2+a3+a4+a5;
	}

	auto set_sum4(int &k, int a1, int a2, int a3, int a4)->void {
		i+=a1+a2+a3+a4;
		k = i;
	}

	auto f8 (int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8) const ->int {
		return i+a1+a2+a3+a4+a5+a6+a7+a8;
	}

	static auto sqr(double x)->double { return x*x; }

	auto g1(const int& n)->void { std::cout << "g1: " << n <<  std::endl; }
	auto g2(int&& n)->void { std::cout << "g2: " << n <<  std::endl; }
	auto g3(int& n)->int&  { n++; return n; }
	auto g4(int n)-<int    { return 10*n; }
};

Please note that in source code above we use C++11 convention using auto and new function declaration syntax. It is not absolutely a requirement to do so, hence you can also define the class using traditional way of declaration like good all C++ program.

In the program, we can create a class as follows:

A a(11)

Now, to create delegates:

[sourcecode language="c++"]
auto set1 = create_delegate(&a,&A::set);
auto inc = create_delegate(&a,&aA::inc);
std::function<int(int&)> incp = create_delegate(&a,&A::incp);
auto af5  = create_delegate(&a,&A::f5);
auto set_sum4= create_delegate(&a,&A::set_sum4);
auto af8  = create_delegate(&a,&A::f8);
auto sqr = create_delegate(&A::sqr); // static function, not a member
auto g1  = create_delegate(&a,&A::g1);
auto g2  = create_delegate(&a,&A::g2);
auto g3  = create_delegate(&a,&A::g3);
auto g4  = create_delegate(&a,&A::g4);
[/sourcecode]

As demonstrated, we can use auto or function<…>. Now we can invoke the delegates:

set1(25);
int x = 5;
int k = inc(x);
int k2 = incp(x);
k2 *= 7;
std::cout << "a.get():" << a.get() << std::endl;
std::cout << "k: " << k << std::endl;
std::cout << "x: " << x << std::endl;
std::cout << "af5(1,2,3,4,5): " << af5(1,2,3,4,5) << std::endl;

set_sum4(x,1,2,3,20);
std::cout << "after set_sum4(x,1,2,3,20)" << std::endl;
std::cout << "a.get(): " << a.get() << std::endl;
std::cout << "x: " << x << std::endl;
std::cout << "af8(1,2,3,4,5,6,7,8): " << af8(1,2,3,4,5,6,7,8) << std::endl;
std::cout << "sqr(2.1): " << sqr(2.1) << std::endl;
g1(100);
g2(32+5);
int p = 15;
int& g = g3(p);
int s = g4(35);
g++;
std::cout << "p: " << p << std::endl;
std::cout << "s: " << s << std::endl;

The program will print:

a.get():30
k: 30
x: 35
af5(1,2,3,4,5): 45
after set_sum4(x,1,2,3,20)
a.get(): 56
x: 56
af8(1,2,3,4,5,6,7,8): 92
sqr(2.1): 4.41
g1: 100
g2: 37
p: 17
s: 350

Points about the implementation

For a simple member function that is not volatile or const, the implementation would be very simple. We have to create a class that will store the two pointers: one to the object and the other to the its member function:

template <class T, class R, class ... P>
struct  _mem_delegate {
	T* m_t;
	R  (T::*m_f)(P ...);
	_mem_delegate(T* t, R  (T::*f)(P ...) ):m_t(t),m_f(f) {}
	R operator()(P ... p) {
			return (m_t->*m_f)(p ...);
	}
};

But the return statement needs some correction. The problem is that if a function has an rvalue-reference argument (like auto g2(int&& n)->void), this will not work. Such a parameter in the body of the function is not an rvalue any more, it’s an lvalue. Then we have two options:

  1. Use forwarding return (m_t->*m_f)(std::forward<P>(p) …);
  2. Just convert using static_cast such that return (m_t->*m_f)(static_cast<P>(p) …);

Both options will work at this point. The first underlines the idea, the second is more general.

The variadic template allows us to define operator() with flexible number and types of parameters. The create_function implementation will simply return an object of this class:

template <class T, class R, class ... P>
_mem_delegate<T,R,P ...> create_delegate(T* t, R (T::*f)(P ...)) {
	_mem_delegate<T,R,P ...> d(t,f);
	return d;
}

Practically, there is needed extra three implementations to cover cases of member functions which are const, volatile and const volatile. That’s why traditional macros, using #define, are handy: they allow us to avoid rewriting the same code fragments. Here is the full implementation:

template <class F>
F* create_delegate(F* f) {
	return f;
}

#define _MEM_DELEGATES(_Q,_NAME)\
template <class T, class R, class ... P>\
struct _mem_delegate ##_NAME\
{\
	T* m_t;\
	R  (T::*m_f)(P ...) _Q;\
	_mem_delegate ##_NAME(T* t, R  (T::*f)(P ...) _Q):m_t(t),m_f(f) {}\
	R operator()(P ... p) _Q\
	{\
		return (m_t->*m_f)(std::forward<P>(p) ...);\
	}\
};\
\
template <class T, class R, class ... P>\
	_mem_delegate ##_NAME<T,R,P ...> create_delegate(T* t, R (T::*f)(P ...) _Q)\
{\
	_mem_delegate ##_NAME<T,R,P ...> d(t,f);\
	return d;\
}

_MEM_DELEGATES(,Z)
_MEM_DELEGATES(const,X)
_MEM_DELEGATES(volatile,Y)
_MEM_DELEGATES(const volatile,W)

A more meaningful example: calculating the length of a curve

In this section we will consider a sample practical problem and look at various approaches, and show some alternative methods to delegates.

Calculation of the length of a curve: global functions

The function CurveLength can be used to calculate the length of a curve, which is defined by two functions fx(t) and fy(t), where t is in the range [a,b]. The parameter n is the number of step we take, and it affects the precision.

auto CurveLength(auto (*fx)(double)->double, auto (*fy)(double)->double, double a, double b,   int n = 20000)
				 ->double
{
	double s = 0.0;
	double h = (b-a)/n;
	double t = a;
	double x0 = fx(t);
	double y0 = fy(t);
	for (int i = 0; i < n; i++) {
		t += h;
		double x1 = fx(t);
		double y1 = fy(t);
		double dx = x1-x0;
		double dy = y1-y0;
		s += sqrt(dx*dx + dy*dy);
		x0 = x1;
		y0 = y1;
	};
	return s;
}

A simple C-style approach would be to define the curves we need as global functions, and if we need to parameterize the curve we will define global variables. We look at an ellipse and a cycloid. The cycloid length is easily calculated analytically. As for the ellipse, if the a and b axes are equal, it will be a circle, otherwise its length cannot be expressed by a simple formula. Below is our definition of the two curves:

//Cycloid

double cycloid_a;
auto Cycloid_fx(double t)->double {
	return cycloid_a*(1 - cos(t));
}

auto Cycloid_fy(double t)->double {
	return cycloid_a*(t - sin(t));
}

//Ellipse
double ellipse_a;
double ellipse_b;

auto Ellipse_fx(double t)->double {
	return ellipse_a*cos(t);
}

auto Ellipse_fy(double t)->double {
	return ellipse_b*sin(t);
}

Here is a sample program to calculate their sizes:

double PI = 4*atan(1.0);

ellipse_a = 1.0;
ellipse_b = 1.0;

cycloid_a = 1.0;

std::cout << std::setprecision(10) << std::setw(10);

std::cout <<  "ellipse1: " << CurveLength(Ellipse_fx, Ellipse_fy, 0.0,2*PI) << std::endl;
std::cout <<  "cycloid1: " << CurveLength(Cycloid_fx, Cycloid_fy, 0.0,2*PI) << std::endl;

ellipse_a = 3.0;
ellipse_b = 1.0;

cycloid_a = 5.0;

std::cout <<  "ellipse2: " << CurveLength(Ellipse_fx, Ellipse_fy, 0.0,2*PI) << std::endl;
std::cout <<  "cycloid2: " << CurveLength(Cycloid_fx, Cycloid_fy, 0.0,2*PI) << std::endl;

 

The program will print:

ellipse1: 6.283185281
cycloid1: 7.999999992
ellipse2: 13.36489317
cycloid2: 39.99999996

The disadvantage of this approach is that all functions and their parameters are global.

An abstract class and virtual functions

A traditional, good C++ approach is to use an abstract class and define a member function CurveLength, which uses the member functions fx(t) and fy(t):

class Curve {
public:
	virtual auto fx(double t)->double = 0;
	virtual auto fy(double t)->double = 0;

	auto CurveLength(double a, double b, int n = 20000)->double {
		double s = 0.0;
		double h = (b-a)/n;
		double t = a;
		double x0 = fx(t);
		double y0 = fy(t);
		for (int i = 0; i < n; i++) {
			t += h;
			double x1 = fx(t);
			double y1 = fy(t);
			double dx = x1-x0;
			double dy = y1-y0;
			s += sqrt(dx*dx + dy*dy);
			x0 = x1;
			y0 = y1;
		}
		return s;
	}
};

class Cycloid: public Curve {
	double m_a;
public:
	Cycloid(double a):m_a(a) {}

	virtual auto fx(double t)->double {
		return m_a*(1 - cos(t));
	}

	virtual auto fy(double t)->double {
		return m_a*(t - sin(t));
	}
};

class Ellipse: public Curve
{
	double m_a;
	double m_b;
public:
    Ellipse(double a, double b):m_a(a),m_b(b) {}

	virtual auto fx(double t)->double {
		return m_a*cos(t);
    }

	virtual auto fy(double t)->double {
		return m_b*sin(t);
	}
};

The program will look like this:

double PI = 4*atan(1.0);

Ellipse ellipse1(1.0,1.0);
Cycloid cycloid1(1.0);

std::cout << std::setprecision(10) << std::setw(10);

std::cout <<  "ellipse1: " << ellipse1.CurveLength(0.0,2*PI) << std::endl;
std::cout <<  "cycloid1: " << cycloid1.CurveLength(0.0,2*PI) << std::endl;

Ellipse ellipse2(3.0,1.0);
Cycloid cycloid2(5.0);

std::cout <<  "ellipse2: " << ellipse1.CurveLength(0.0,2*PI) << std::endl;
std::cout <<  "cycloid2: " << cycloid1.CurveLength(0.0,2*PI) << std::endl;

 

The problem is that all the curves have to be derived from the same base class, and if we want to use CurveLength in a library the class Curve should be part of it.

Using delegates

Delegates allow us to deal with unrelated classes. Here is the approach using delegates:

auto CurveLength(std::function<double(double)> fx, std::function<double(double)> fy, double a, double b, int n = 20000)->double {
	double s = 0.0;
	double h = (b-a)/n;
	double t = a;
	double x0 = fx(t);
	double y0 = fy(t);
	for (int i = 0; i < n; i++) {
		t += h;
		double x1 = fx(t);
		double y1 = fy(t);
		double dx = x1-x0;
		double dy = y1-y0;
		s += sqrt(dx*dx + dy*dy);
		x0 = x1;
		y0 = y1;
	}
	return s;
}

double PI = 4.0*atan(1.0);

class Cycloid {
	double m_a;
public:
	Cycloid(double a):m_a(a) {}

	auto fx(double t)->double {
		return m_a*(1 - cos(t));
	}

	auto fy(double t)->double {
		return m_a*(t - sin(t));
	}
};

class Ellipse {
	double m_a;
	double m_b;
public:
	Ellipse(double a, double b):m_a(a),m_b(b) {}

	auto getX(double t)->double {
		return m_a*cos(t);
	}

	auto getY(double t)->double {
		return m_b*sin(t);
	}
};

 

I have deliberately given different names to member functions. These two classes are unrelated. Here is a program:

int main() {
	Ellipse ellipse1(1.0,1.0);
	Cycloid cycloid1(1.0);

	auto ellipse1_fx = create_delegate(&ellipse1,&Ellipse::getX);
	auto ellipse1_fy = create_delegate(&ellipse1,&Ellipse::getY);
	auto cycloid1_fx = create_delegate(&cycloid1,&Cycloid::fx);
	auto cycloid1_fy = create_delegate(&cycloid1,&Cycloid::fy);

	std::cout << std::setprecision(10) << std::setw(10);

	std::cout <<  "ellipse1: " << CurveLength(ellipse1_fx, ellipse1_fy, 0.0,2*PI) << std::endl;
	std::cout <<  "cycloid1: " << CurveLength(cycloid1_fx, cycloid1_fy, 0.0,2*PI) << std::endl;

	Ellipse ellipse2(3.0,1.0);
	Cycloid cycloid2(5.0);
	auto ellipse2_fx = create_delegate(&ellipse2,&Ellipse::getX);
	auto ellipse2_fy = create_delegate(&ellipse2,&Ellipse::getY);
	auto cycloid2_fx = create_delegate(&cycloid2,&Cycloid::fx);
	auto cycloid2_fy = create_delegate(&cycloid2,&Cycloid::fy);

	std::cout <<  "ellipse2: " << CurveLength(ellipse2_fx, ellipse2_fy, 0.0,2*PI) << std::endl;
	std::cout <<  "cycloid2: " << CurveLength(cycloid2_fx, cycloid2_fy, 0.0,2*PI) << std::endl;

	return 0;
}

 

Delegates allow us to use the same functions for various curve classes, which are completely unrelated. This CurveLength can be part of the library.

An alternative approach: using lambdas

The C++11 lambdas make it possible to define functions immediately and also to capture the variables from the environment. The CurveLength and the curve classes can be defined exactly the same as in the example with delegates, but the program, the function calls change:

int main() {
	Ellipse ellipse1(1.0,1.0);
	Cycloid cycloid1(1.0);

	auto ellipse1_fx = create_delegate(&ellipse1,&Ellipse::getX);
	auto ellipse1_fy = create_delegate(&ellipse1,&Ellipse::getY);
	auto cycloid1_fx = create_delegate(&cycloid1,&Cycloid::fx);
	auto cycloid1_fy = create_delegate(&cycloid1,&Cycloid::fy);

	std::cout << std::setprecision(10) << std::setw(10);

	std::cout <<  "ellipse1: " << CurveLength(ellipse1_fx, ellipse1_fy, 0.0,2*PI) << std::endl;
	std::cout <<  "cycloid1: " << CurveLength(cycloid1_fx, cycloid1_fy, 0.0,2*PI) << std::endl;

	Ellipse ellipse2(3.0,1.0);
	Cycloid cycloid2(5.0);
	auto ellipse2_fx = create_delegate(&ellipse2,&Ellipse::getX);
	auto ellipse2_fy = create_delegate(&ellipse2,&Ellipse::getY);
	auto cycloid2_fx = create_delegate(&cycloid2,&Cycloid::fx);
	auto cycloid2_fy = create_delegate(&cycloid2,&Cycloid::fy);

	std::cout <<  "ellipse2: " << CurveLength(ellipse2_fx, ellipse2_fy, 0.0,2*PI) << std::endl;
	std::cout <<  "cycloid2: " << CurveLength(cycloid2_fx, cycloid2_fy, 0.0,2*PI) << std::endl;

	return 0;
}

I deliberately show two approaches to capture variables: a specific one [&ellipse1] , and general [&]. Either can be used. Lambdas are easy to use, but syntax becomes longer if you have to deal with more parameters. In the end, it’s your choice.

, ,

About Author

about author

xathrya

A man who is obsessed to low level technology.

1 Comment
  1. C++11 New Feature: Variadic Template - Xathrya.ID

    […] proven themselves even more powerful. Variadic templates are a trusworthy solution to implement delegates and tuples. And, instead of C-style ellipsis mechanism, variadic templates can offer a typesafer […]

Leave a Reply

Your email address will not be published. Required fields are marked *

Social media & sharing icons powered by UltimatelySocial