See what's going on with flipcode!




This section of the archives stores flipcode's complete Developer Toolbox collection, featuring a variety of mini-articles and source code contributions from our readers.

 

  Using The CPP For Metaprogramming
  Submitted by



The C preprocessor is dreaded by many programmers. Despite the limitations of the CPP, it can be used for simple metaprogramming, which can save a lot of typing. Unless you can eat while typing, like some programmers do, you might enjoy to add this technique to your bag of tricks.

Please note that most code in this article has been written directly into this article and has never passed through a compiler. While I'm pretty good at spotting typing errors in C++ code, most compilers are still much better than me.

Generating program components using macros



Suppose you are writing a vector or an arithmetic array class and you want to implement all kinds of component-wise operators. The + and - operators could look like this:

template<class T, int n
Vec<T,n operator+(const Vec<T,n& lhs, const Vec<T,n& rhs)
{
  Vec<T,n res;

for (int i=0; i<n; ++i) res[i] = lhs[i] + rhs[i];

return res; }

template<class T, int n Vec<T,n operator-(const Vec<T,n& lhs, const Vec<T,n& rhs) { Vec<T,n res;

for (int i=0; i<n; ++i) res[i] = lhs[i] - rhs[i];

return res; }




As you can see there is hardly any difference between the two routines. Indeed, if you would print the routines on transparent film and put them on top of each other, you would only see the first version. Essentially the two routines are token for token identical, except for the operators + and -.



But you are not through yet, in addition to the above two operators, you would have to write the same kind of code for *,/,%,&,^,|, etc... depending on your requirements. You can probably imagine that the amount of code would get quickly out of hand. Isn't there a more concise way to express this?

Well, with some simple generic programming, we could factor out the loop, but that is not the point of this article. Unfortunately, the C++ template mechanism just isn't powerful enough for what we are trying to do. The CPP, however, does not have that limitation. What we can do is to create a macro, parameterized by the variable parts of the functions, and then use that macro to generate the functions:



#define UMP_DEF_VEC_OP(op)      \
  template<class T, int n      \
  Vec<T,n operator op(const Vec<T,n& lhs, const Vec<T,n& rhs)\
  {                             \
    Vec<T,n res;               \
                                \
    for (int i=0; i<n; ++i)     \
      res[i] = lhs[i] op rhs[i];\
                                \
    return res;                 \
  }
UMP_DEF_VEC_OP(*)
UMP_DEF_VEC_OP(/)
UMP_DEF_VEC_OP(%)
UMP_DEF_VEC_OP(+)
UMP_DEF_VEC_OP(-)
UMP_DEF_VEC_OP(<<)
UMP_DEF_VEC_OP()
UMP_DEF_VEC_OP(&)
UMP_DEF_VEC_OP(^)
UMP_DEF_VEC_OP(|)
#undef UMP_DEF_VEC_OP 




This example can be taken a step further by defining the corresponding compound assignment and scalar operators as well:



#define UMP_DEF_VEC_OP(op)      \
  template<class T, int n      \
  Vec<T,n& operator op##=(Vec<T,n& lhs, const Vec<T,n& rhs)\
  {                             \
    for (int i=0; i<n; ++i)     \
      lhs[i] op##= rhs[i];      \
                                \
    return lhs;                 \
  }                             \
                                \
  template<class T, int n      \
  Vec<T,n operator op(const Vec<T,n& lhs, const Vec<T,n& rhs)\
  {                             \
    Vec<T,n res;               \
                                \
    for (int i=0; i<n; ++i)     \
      res[i] = lhs[i] op rhs[i];\
                                \
    return res;                 \
  }                             \
                                \
  template<class T, int n      \
  Vec<T, n operator op(const Vec<T,n& lhs, T rhs)\
  {                             \
    Vec<T,n res;               \
                                \
    for (int i=0; i<n; ++i)     \
      res[i] = lhs[i] op rhs;   \
                                \
    return res;                 \
  }                             \
                                \
  template<class T, int n      \
  Vec<T, n operator op(T lhs, const Vec<T,n& rhs)\
  {                             \
    Vec<T,n res;               \
                                \
    for (int i=0; i<n; ++i)     \
      res[i] = lhs op rhs[i];   \
                                \
    return res;                 \
  }
UMP_DEF_VEC_OP(*)
UMP_DEF_VEC_OP(/)
UMP_DEF_VEC_OP(%)
UMP_DEF_VEC_OP(+)
UMP_DEF_VEC_OP(-)
UMP_DEF_VEC_OP(<<)
UMP_DEF_VEC_OP()
UMP_DEF_VEC_OP(&)
UMP_DEF_VEC_OP(^)
UMP_DEF_VEC_OP(|)
#undef UMP_DEF_VEC_OP 




Hopefully you are starting to see that this kind of metaprogramming can save you a LOT of typing. Not only does it save you typing, it also makes it more difficult to make errors, because it is much easier to spot an error when you only have to check the macro parameters without having to browse through huge amounts of repeated code.



Here is an algorithm for writing the above kind of macro:
  • 1. Write the complete definition of the kind of program element you wish to generate. This way it is easy to get it right.
  • 2. Select the code you just wrote and replace each occurance of the token you wish to generate with a parameter token. You may need to use the token pasting operator ## (see your C/C++ manual).
  • 3. Write the macro definition. Remember that preprocessor statements must begin at the start of line in standard C++. Remember to use the line continuation character on every line except the last.
  • 4. Write the macro undefinition. This is important, because otherwise you will quickly litter the global namespace (*).
  • 5. Use the macro to generate the program elements you wanted.
  • 6. Take a moment to reflect upon your work, then ask your boss for a raise, because you are now working n-times faster.



  • (*) Technically, macros are not related to namespaces.



    From O(n**2) to O(1) typing

    The above example of generating the vector operators is just one example of how you can take advantage of the CPP. I will present one more technique, which can prove useful sometimes.

    The CPP token pasting operator can be used to define tokens based on macro parameters. This way you can generate finite lists neatly using the CPP. For example, with suitable definitions, the following code

    #define UMP_LIST_ELEM(i) i
    #define UMP_LIST_SEP(i)  ,

    int numbers[] = {UMP_LIST(10, UMP_LIST_SEP, UMP_LIST_ELEM)};

    #undef UMP_LIST_SEP #undef UMP_LIST_ELEM




    would generate the following code:

    int numbers[] = {0,1,2,3,4,5,6,7,8,9}; 


    Now, what you just saw probably doesn't rock your balls, but I'll give a stiffer example. Let's get back to writing the vector class. Suppose we want to write suitable constructors for our generic vector class:



    template<class T, int n
    class Vec {
      T m_v;
    public:
      Vec()
      {}

    Vec(T t_0) {m_v[0] = t_0;}

    Vec(T t_0, T t_1) {m_v[0] = t0; m_v[1] = t_1;}

    Vec(T t_0, T t_1, T t_2) {m_v[0] = t_0; m_v[1] = t_1; m_v[2] = t_2;}

    Vec(T t_0, T t_1, T t_2, T t_3) {m_v[0] = t_0; m_v[1] = t_1; m_v[2] = t_2; m_v[3] = t_3;}

    // And so on... };




    Writing the constructor definitions involves typing O(n**2) tokens, where n is the amount of arguments you want for the biggest constructor. That is a LOT of typing! Fortunately we can use the CPP to generate this stuff for us using essentially O(1) tokens. Actually I'm cheating a bit, because I'm assuming that we have previously written the reusable preprocessor helpers that I now present:





    #define UMP_LIST(n, SEP, ELEM)  UMP_LIST_##n(SEP, ELEM)
    #define UMP_LIST_0(SEP, ELEM)
    #define UMP_LIST_1(SEP, ELEM)                                  ELEM(0)
    #define UMP_LIST_2(SEP, ELEM)   UMP_LIST_1(SEP, ELEM)  SEP(1)  ELEM(1)
    #define UMP_LIST_3(SEP, ELEM)   UMP_LIST_2(SEP, ELEM)  SEP(2)  ELEM(2)
    #define UMP_LIST_4(SEP, ELEM)   UMP_LIST_3(SEP, ELEM)  SEP(3)  ELEM(3)
    #define UMP_LIST_5(SEP, ELEM)   UMP_LIST_4(SEP, ELEM)  SEP(4)  ELEM(4)
    #define UMP_LIST_6(SEP, ELEM)   UMP_LIST_5(SEP, ELEM)  SEP(5)  ELEM(5)
    #define UMP_LIST_7(SEP, ELEM)   UMP_LIST_6(SEP, ELEM)  SEP(6)  ELEM(6)
    #define UMP_LIST_8(SEP, ELEM)   UMP_LIST_7(SEP, ELEM)  SEP(7)  ELEM(7)
    #define UMP_LIST_9(SEP, ELEM)   UMP_LIST_8(SEP, ELEM)  SEP(8)  ELEM(8)
    #define UMP_LIST_10(SEP, ELEM)  UMP_LIST_9(SEP, ELEM)  SEP(9)  ELEM(9)
    // ...
    
    #define UMP_LST2(n, SEP, ELEM)  UMP_LST2_##n(SEP, ELEM)
    #define UMP_LST2_0(SEP, ELEM)
    #define UMP_LST2_1(SEP, ELEM)                                  ELEM(0)
    #define UMP_LST2_2(SEP, ELEM)   UMP_LST2_1(SEP, ELEM)  SEP(1)  ELEM(1)
    #define UMP_LST2_3(SEP, ELEM)   UMP_LST2_2(SEP, ELEM)  SEP(2)  ELEM(2)
    #define UMP_LST2_4(SEP, ELEM)   UMP_LST2_3(SEP, ELEM)  SEP(3)  ELEM(3)
    #define UMP_LST2_5(SEP, ELEM)   UMP_LST2_4(SEP, ELEM)  SEP(4)  ELEM(4)
    #define UMP_LST2_6(SEP, ELEM)   UMP_LST2_5(SEP, ELEM)  SEP(5)  ELEM(5)
    #define UMP_LST2_7(SEP, ELEM)   UMP_LST2_6(SEP, ELEM)  SEP(6)  ELEM(6)
    #define UMP_LST2_8(SEP, ELEM)   UMP_LST2_7(SEP, ELEM)  SEP(7)  ELEM(7)
    #define UMP_LST2_9(SEP, ELEM)   UMP_LST2_8(SEP, ELEM)  SEP(8)  ELEM(8)
    #define UMP_LST2_10(SEP, ELEM)  UMP_LST2_9(SEP, ELEM)  SEP(9)  ELEM(9)
    // ... 


    The repetition (and the second list) is required, because the CPP does not allow recursion. Now we are ready to write the constructors:



    template<class T, int n
    class Vec {
      T m_v;
    public:
    #define UMP_ARG_SEP(i)     ,
    #define UMP_ARG_ELEM(i)    T t_##i
    #define UMP_INIT_ELEM(i)   m_v[i] = t_##i;
    #define UMP_EMPTY(i)

    #define UMP_DEF_CTOR(n) \ Vec(UMP_LIST(n, UMP_ARG_SEP, UMP_ARG_ELEM))\ {UMP_LIST(n, UMP_EMPTY, UMP_INIT_ELEM)}

    UMP_LST2(10, UMP_EMPTY, UMP_DEF_CTOR)

    #undef UMP_ARG_SEP #undef UMP_ARG_ELEM #undef UMP_INIT_ELEM #undef UMP_EMPTY };




    As an exercise, try writing the constructors by hand for upto something like 20 arguments. You'll soon find that it takes plenty of time. Then try using the previous technique. You should notice that the amount of code generating the constructors stays the same. Only the reusable CPP list generation code needs to be extended upto the desired length.



    Obviously you don't have to go this far to get benefits from using the CPP, but you don't have to stop here either. And don't you dare take this as a permit to use the CPP to define constants!



    Regards,
    Vesa Karvonen
    Housemarque, Inc.


    The zip file viewer built into the Developer Toolbox made use of the zlib library, as well as the zlibdll source additions.

     

    Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
    Please read our Terms, Conditions, and Privacy information.