Introduction
It often happens that we define an object inside a class and want to define methods in the latter to access some methods in the former. Sometimes these methods are called pass-through methods. Unfortunately the techniques used here don’t work in Visual C++ 2012; they work in GNU C++ compiler, version 4.7.0 or higher with the std=c++11
option switched on.
Demonstration of the Problem
First of all, let's define a class that we’ll use for an enclosed object:
typedef std::vector<double> block;
class A
{
block x;
public:
A(const block& x1):x(x1) {}
A(block&& x1):x(std::move(x1)) {}
block add(const block& y)
{
std::cout << "use copy, non-const" << std::endl;
std::size_t n = x.size();
for (std::size_t i = 0; i < n; i++)
{
x[i] += y[i];
}
return x;
}
block add(const block& y) const
{
std::cout << "use copy, const" << std::endl;
std::size_t n = x.size();
block z = y;
for (std::size_t i = 0; i < n; i++)
{
z[i] += x[i];
}
return z;
}
block add(block&& y) const
{
std::cout << "use move, const" << std::endl;
std::size_t n = x.size();
for (std::size_t i = 0; i < n; i++)
{
y[i] += x[i];
}
return std::move(y);
}
block get() const { return x;}
};
I have deliberately given an example with r-value references and some const-qualifiers to demonstrate that the proposed technique works for various cases.
Now, let’s define the class itself:
class B
{
A a;
public:
B(const block& x1):a(x1) {}
B(block&& x1):a(std::move(x1)) {}
block add(const block& y) { return a.add(y); }
block add(const block& y) const { return a.add(y);}
block add(block&& y) { return a.add(std::move(y));}
block get() const { return a.get(); }
};
In the real world we would put some extra functionality inside the class B
. Here is the rest of the program:
void print(const std::string& name, const block& v)
{
std::cout << name;
std::size_t n = v.size();
std::cout << "(" << n << "): ";
for (std::size_t i = 0; i < n; i++)
{
std::cout << v[i] << (i == n-1 ? "" : ", ");
}
std::cout << "\n" << std::endl;
}
int main()
{
block z;
z.push_back(10.0);
z.push_back(20.0);
z.push_back(30.0);
B b1(z);
const B b2(z);
const B b3(z);
block p;
p.push_back(100.0);
p.push_back(111.0);
p.push_back(3000.0);
block r1 = b1.add(p);
print("p",p);
print("r1",r1);
block x1 = b1.get();
print("x1",x1);
block r2 = b2.add(p);
print("p",p);
print("r2",r2);
block x2 = b2.get();
print("x2",x2);
block r3 = b3.add(std::move(p));
print("p",p);
print("r3",r3);
block x3 = b3.get();
print("x3",x3);
return 0;
}
use copy, non-const
p(3): 100, 111, 3000
r1(3): 110, 131, 3030
x1(3): 110, 131, 3030
use copy, const
p(3): 100, 111, 3000
r2(3): 110, 131, 3030
x2(3): 10, 20, 30
use move, const
p(0):
r3(3): 110, 131, 3030
x3(3): 10, 20, 30
As you may see, all the three add
functions produce the same results, but behind the scenes they
do different things: the first one changes the member-variable x
, the second — does not change anything, the third —changes the parameter (actually “moves” it into the result).
We had to write the following definitions:
block add(const block& y) { return a.add(y); }
block add(const block& y) const { return a.add(y);}
block add(block&& y) { return a.add(std::move(y));}
block get() const { return a.get(); }
Although these definitions look simple, they are error-prone and we are looking for simpler ways of writing them.
Simple Alias Definitions for Member Functions
The technique that we are going to use is called perfect forwarding, which enables to define one template that is suitable for defining several function: with a reference parameter, with a constant reference parameter and with an r-value reference parameter. Here is the template definition:
template<class T>
block add(T&& y)
{
return a.add(static_cast<T&&>(y));
}
But we have to write the same for const
qualifiers as well:
template<class T>
block add(T&& y) const
{
return a.add(static_cast<T&&>(y));
}
We can write the following macros:
#define memb_alias_impl1(_a,_memb,_cv)\
template <class T>\
block
_memb(T&& y) _cv\
{\
return _a._memb(static_cast<T&&>(y));\
}
#define memb_alias(_a,_memb)\
memb_alias_impl1(_a,_memb,)\
memb_alias_impl1(_a,_memb,const)
This all looks good, except that there still the block
type returned. We want this type to be derived from _a._memb
. In order to do that, let’s define the following class
template<class _R, class _T, class _Arg>
struct memb_types
{
typedef _R return_type;
typedef _T class_type;
memb_types(_R (_T::*f)(_Arg)) {}
};
If the right parameter (the member function ) is supplied to the constructor, the class will be created with the right types.
template<class _R, class _T, class _Arg>
memb_types<_R, _T, _Arg> get_memb_types(_R (_T::*f)(_Arg))
{
return memb_types<_R, _T, _Arg>(f);
}
We can do the same with the const
qualifier:
template<class _R, class _T, class _Arg>
struct memb_types_const
{
typedef _R return_type;
typedef _T class_type;
memb_types_const(_R (_T::*f)(_Arg) const) {}
};
template<class _R, class _T, class _Arg>
memb_types_const<_R, _T, _Arg> get_memb_types(_R (_T::*f)(_Arg) const)
{
return memb_types_const<_R, _T, _Arg>(f);
}
Now we can define memb_alias_impl1
and memb_alias
properly:
#define memb_alias_impl1(_a,_memb,_cv)\
template <class Args>\
typename decltype(get_memb_types(&decltype(_a)::_memb))::return_type _memb(Args&& y) _cv\
{\
return _a._memb(std::forward<Args>(y) );\
}
#define memb_alias(_a,_memb)\
memb_alias_impl1(_a,_memb,)\
memb_alias_impl1(_a,_memb,const)
Then the same should be done for functions with no parameters (there only one in our program): we will create memb_alias0
. Now we can rewrite the class B
as follows:
class B
{
A a;
public:
B(const block& x1):a(x1) {}
B(block&& x1):a(std::move(x1)) {}
memb_alias (a,add);
memb_alias0 (a,get);
};
The definitions of methods is much shorter and less error-prone. Imagine if you had to write more of such aliases and for several classes.
The problem is that we have to write a separate member-function alias for every number of parameters we want to use: memb_alias2
, memb_alias3
, etc. This is not convenient, although possible. A better technique is to provide one memb_alias
for all the member functions. In order to do that we have to use variadic
templates.
Advanced Techniques: Using Variadic Templates
With variadic templates we can immediately deal with all the numbers of parameters. The memb_types
structure and the get_member_types
function will be defined like this:
template<class _R, class _T, class ... _Arg>
struct memb_types
{
typedef _R return_type;
typedef _T class_type;
memb_types(_R (_T::*f)(_Arg...)) {}
};
template<class _R, class _T, class ..._Arg>
memb_types<_R, _T, _Arg...> get_memb_types(_R (_T::*f)(_Arg...))
{
return memb_types<_R, _T, _Arg...>(f);
}
Obviously we have to define the same constructs for const
member functions as well. After that memb_alias_impl <code>
<code>can be defined as follows:
#define memb_alias_impl(_a,_memb,_cv)\
template <class ... Args>\
decltype(get_memb_types(&decltype(_a)::_memb))::return_type
_memb(Args&& ... y) _cv\
{\
return _a._memb(std::forward<Args>(y) ... );\
}
Now the definition of memb_alias
will look rather simple:
#define memb_alias(_a,_memb)\
memb_alias_impl(_a,_memb,)\
memb_alias_impl(_a,_memb,const)
Now the methods in the class B
can be rewritten as follows:
memb_alias (a,add);
memb_alias (a,get);
You may want to check that the program will print exactly the same.