12

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?

Community
  • 1
  • 1
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • @Marcelo: Yeah. I realized that after posting the code. But any improvement, can we avoid it? – Nawaz May 21 '11 at 06:55
  • @Nawaz: Sorry, I realised my comment was actually an answer, so I moved it. – Marcelo Cantos May 21 '11 at 06:57
  • @Downvoters: Why downvote? Is it not a QUESTION, asking improvement/correction? – Nawaz May 21 '11 at 06:59
  • @Nawaz - Questions can be bad too. Why are you trying to invent a macro to do what for example `generate()` from can already do? – Bo Persson May 21 '11 at 09:06
  • @Bo Persson: What is bad in the question? Can't I try something which I don't know yet? Can't I explore the language in a way I never did before? Beside, I don't know `generate()` can be used to fill arrays in the *initialization*. Please show me with an example. – Nawaz May 21 '11 at 09:10
  • @Nawaz - Macros are rarely any good. Non-working macros are really bad. It is true that `generate()` is code on a separate line, but has the advantage that it has a defined order. – Bo Persson May 21 '11 at 09:21
  • @Bo Persson: Thanks for the info. But that isn't my question. I am exploring macro, not template. – Nawaz May 21 '11 at 09:23

5 Answers5

5

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.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • +1. Awesome. Can we write such macro ourselves (for C++)? How has it been written? – Nawaz May 21 '11 at 09:18
  • @Nawaz, this is by no means straight forward, unfortunately. You'd really have to look into P99 for the details. (Or on my blog for some explanations). Many C++ compilers have variable arguments as an extension, so there it could just work. – Jens Gustedt May 21 '11 at 11:21
5

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.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • Ahh, finally, I get to see one interesting solution. +100000. – Nawaz May 21 '11 at 14:21
  • Btw, can you explain this a bit, at least the top level description of each part? – Nawaz May 21 '11 at 14:23
  • @Nawaz: I must admit I am guily of using this library, even at work. My personal favs are: `BOOST_PP_ENUM_PARAM`, which I have used to generate overloads of various arities for functions (from 0 to 9 usually) because we're still stuck with C++03 at work, and the `BOOST_PP_SEQ` collection, because sequences let you simulate variadic macro with some ease. – Matthieu M. May 21 '11 at 14:36
  • thanks for the description of the parts. btw, doc entry for `BOOST_PP_ENUM` seems to be wrong, I think you didn't intend this link. – Nawaz May 21 '11 at 14:58
  • @Nawaz: thanks for the bad link, it seems they changed the website and use i-frames now... hum :/ – Matthieu M. May 21 '11 at 15:02
3

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.

Marcelo Cantos
  • 181,030
  • 38
  • 327
  • 365
  • I think it's 'unspecified' now, not 'undefined'. – Yakov Galka May 21 '11 at 06:59
  • @ybungalobill: I lack the time to investigate and thus agree or disagree, but yes, that sounds about right. – Marcelo Cantos May 21 '11 at 07:00
  • @Marcelo: I think comma in array initialization is not same as comma operator (in general). I maybe wrong. What does the Standard say? – Nawaz May 21 '11 at 07:01
  • @Marcelo: Can you give example of `Boost.Preprocessor` which generates sequences. Can it fill array values? – Nawaz May 21 '11 at 07:09
  • @Nawaz: Yes, the comma operator is different to the comma used in array initialisation, but neither usage introduces a sequence point. The comma operator is free to evaluate its arguments in reverse order, as long it "returns" the last argument. – Marcelo Cantos May 21 '11 at 23:47
  • @Nawaz: Boost.Preprocessor is replete with counting constructs; have a look at the ENUM* family of macros in the [reference](http://www.boost.org/doc/libs/1_46_1/libs/preprocessor/doc/) section. – Marcelo Cantos May 21 '11 at 23:48
2

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.

iammilind
  • 68,093
  • 33
  • 169
  • 336
  • If you want to use template, then no need to write your own. You can use existing one: http://www.ideone.com/hQG8l .. But then +1 for the attempt. :-) – Nawaz May 21 '11 at 09:52
  • @Nawaz, i feel that above template solution would do most of the things at compile time and will give almost the same effect as, `int a[100] = { 1, 2, 3, ..., 99 };` (it will avoid the runtime `i++` kind of operations for larger arrays). – iammilind May 21 '11 at 12:01
  • It does nothing at compile-time as far as array is concerned. There is one function call for each array element. – Nawaz May 21 '11 at 12:06
  • @Nawaz, my indication was towards my template solution; it will be done at compile time. However, I can't give guarantee, but I **feel** that compiler should be able to optimize the code and make a line of `p[INDEX] = VALUE;` in actual code which will be equivalent to `int a[100] = { 1, 2, 3, ..., 99 };`. But again, my knowledge is lacking for compilers. :) – iammilind May 21 '11 at 12:14
  • I'm also talking about your template solution. There are exactly 101 function calls. 1 call to `Initialize` function, and other `100` calls to `Assign` function in each instantiated template. – Nawaz May 21 '11 at 12:21
1

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

Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274
  • that will take multiple compilation and running the code; I think OP wants a simpler solution or enhancement. – iammilind May 21 '11 at 07:38
  • +1. I liked the idea which is in fact based on the idea I already implemented. Also, it generates the macros using different program. Can it be merged? – Nawaz May 21 '11 at 07:41
  • @Nawaz, for merging these 2; we have to enhance the above program, which will first put the `#define` and then run the actual executable. – iammilind May 21 '11 at 08:14
  • @iammilind: the second link to ideone is merging, but that is not what I meant. I meant, can we write less number of macros doing more and more works, of varying type, combinatorially, as @Benjamin's solution has too much of macros. They're not much generic. – Nawaz May 21 '11 at 08:20