Click here to Skip to main content
15,886,873 members
Articles / Programming Languages / C++11

Macros for Defining Method Aliases for a Member Object

Rate me:
Please Sign up or sign in to vote.
4.83/5 (6 votes)
2 Jun 2013CPOL3 min read 17K   85   6   3
The article shows the techniques for defining pass-through methods

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:
    
    // constructors
    B(const block& x1):a(x1) {}
    B(block&& x1):a(std::move(x1)) {}
 
    // methods
    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:   

// an auxiliary function for printing
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;
} 

This program will print: 

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: 

//an auxiliary macro 
#define memb_alias_impl1(_a,_memb,_cv)\
template <class T>\
block
_memb(T&& y) _cv\
{\
    return _a._memb(static_cast<T&&>(y));\
}
  
//a member alias for a single-parameter member function
#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:
    
    // constructors
    B(const block& x1):a(x1) {}
    B(block&& x1):a(std::move(x1)) {}
   // methods
   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_alias2memb_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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questiongreat article Pin
Alex Jonhson2-Jun-13 12:14
Alex Jonhson2-Jun-13 12:14 
GeneralMy vote of 5 Pin
Alex Jonhson2-Jun-13 12:14
Alex Jonhson2-Jun-13 12:14 
GeneralRe: My vote of 5 Pin
Mikhail Semenov2-Jun-13 23:33
Mikhail Semenov2-Jun-13 23:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.