开发者

Trick : filling array values using macros (code generation)

Are C++ Templates just Macros in disguise?

I was reading the above topic, and suddenly this idea came to my mind: why not try writing some tricky macros which can be used in our real code, (not just only as puzzles which are useless in real life)?

So the first thing came to mind is : filling array values with macros:

int f(int &i) { return ++i; }

#define e100     r5(m20)
#define m20      m5,m5,m5,m5
#define m5       r5(e1)
#define e1   开发者_运维问答    f(i)  //avoiding ++i right here, to avoid UB!
#define r5(e)    e,e,e,e,e

int main() {
        int i=0;           //this is used in the macro e1
        int a[] = {e100};  //filling array values with macros!
        int n  = sizeof(a)/sizeof(int);
        cout << "count = " << n << endl;
        for(int i = 0 ; i < n ; i++ ) 
            cout << a[i] << endl;
        return 0;
}

Output:

count = 100
1
2
3
4
.
.
.
100

Online demo : http://www.ideone.com/nUYrq

Can we further improve this solution in terms of compactness or genericity (possibly both)? Can we get rid of the variable i which we need in the macro? Or any other improvement?

I would also like to know if that is valid code both in C++ and C (of course ignoring printing part)?

EDIT:

I realized that the order of calls to f() seems still unspecified. I'm not sure though, as I think comma in array initialization is not probably same as comma operator (in general). But if it is, can we avoid it and what part of the Standard says its unspecified?


If you wish to delve into Preprocessor programming, I can only recommend the Boost.Preprocessor library as a building block, you'll avoid having to rewrite things from scratch.

For example, in order to create your table, I would have used (ideone):

#include <iostream>

#include <boost/preprocessor/repetition/enum.hpp>

#define ORDER(z, n, text) n

int main() {
  int const a[] = { BOOST_PP_ENUM(100, ORDER, ~) };
  std::size_t const n = sizeof(a)/sizeof(int);

  std::cout << "count = " << n << "\n";

  for(std::size_t i = 0 ; i != n ; ++i ) 
    std::cout << a[i] << "\n";

  return 0;
}

And leave all the cruft to Boost :)

Note: this enumerates from 0 to 99, not 1 to 100, there are other operations available to perform arithmetic ;)

EDIT: How does this work ?

First, I can only recommend the doc entry for BOOST_PP_ENUM

BOOST_PP_ENUM is a macro which takes 3 arguments: (n, MACRO, data)

  • n: an integer
  • MACRO: a macro accepting 3 arguments: (z, i, data)
  • data: some data, of your convenience, to be passed to macro

It will then be replaced by n successive invocations of MACRO separated by commas:

MACRO(z, 0, data), MACRO(z, 1, data), ... , MACRO(z, n-1, data)

It is up to you to do whatever you wish with your MACRO.

I am afraid I have never used the z argument, it is used internally, and you could in theory use it to speed up the process.


P99 has a macro that does exactly what you want

#include "p99_map.h"

int Ara[] = { P99_POSS(100) };

It has the advantage that it is entirely compile time, no dynamic initialization with functions etc at all.

For you, it probably has the disadvantage that it uses C99 features, in particular macros with variable length arguments.


No, this is not valid code; the behaviour is still undefined. Since there are no sequence points between array-initialisation elements, the calls to f() may occur in any order.

It is possible to generate sequences. Boost.Preprocessor does so, and uses such sequences to emit much more interesting stuff.


I think still the template would provide superior solution which will be definite and less error prone. See the following code; many of things are calculated at compile time only and it should generate efficient code.

template<int VALUE, int INDEX, int SIZE, bool ALLOW>
struct Assign
{
  static void Element (int *p) 
  {
    Assign<VALUE + 1, INDEX + 1, SIZE, (INDEX < SIZE)>::Element(p);
    p[INDEX] = VALUE;
  }
};
template<int VALUE, int INDEX, int SIZE>
struct Assign<VALUE, INDEX, SIZE, false>
{
  static void Element (int *p) { p[INDEX] = VALUE; }
};

template<int START, int SIZE>
void Initialize (int (&a)[SIZE])
{
  Assign<START, 0, SIZE, true>::Element(a);
}

It might be little complex at first glance, but understandable. It can be still made more general. Usage will be as simple as following:

int a[100];
Initialize<1>(a);  // '1' is the starting value

This can be used for any int a[N]. Here is the output of the code.


How about some simple code generation.

#include <fstream> 

int main() {

    std::ofstream fout("sequence_macros.hpp");

    for(int i=1; i<=100; ++i)
    {
        fout << "#define e" << i << "(a) ";
        fout << "(a+0)";
        for(int j=1; j<i; ++j)
        {
            fout << ",(a+" << j << ")";
        }
        fout << '\n';
    }
}

Then putting it to use:

#include <iostream>
#include "sequence_macros.hpp"

int main()
{
   // Create an array with 157 elements, in
   // sequence, starting at 25
   int x[] = {e100(25),e50(25+100),e7(25+100+50)};
   int sz = sizeof(x) / sizeof(*x);
   for(int i=0; i<sz; ++i)
      std::cout << x[i] << '\n';
}

Generating the code: http://www.ideone.com/iQjrj

Using the code: http://ideone.com/SQikz

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜