metaprogramming with the preprocessor: when is it too much? (inspiredby the iterators thread)

M

Mark Piffer

Hi c.l.c,

after reading through a sufficient part of the iterator-thread, I
remembered an iterator-like preprocessing facility which I wrote a
while back. The aim was to provide zero-overhead ad-hoc iterator
functionality through the C preprocessor. I will roughly depict below
what I came up with:

DISCLAIMER: as the preprocessor symbols are heavily used in the code
and not everyone likes being constantly shouted at when reading
source, please mentally replace the all-caps notation with something
more friendly. And in case you come up with much nicer or more logical
names for the operations, I am all ears.

an iterator definition looks like this:
#define pp_itername
ITER_DEF(name,start,end,DEREF(deref_exp,NEXT(next_stmt,ATEND(atend_exp,....ITER_ENDDEF)))))

The pp_itername will subsequently be used in operations, e.g.
ITER_NEXT(pp_itername)
x = ITER_GET(pp_itername)

Of course, pp_itername will have vanished by the time the compilers
gets to see the code. So this looks a bit unusual as there is another
level of indirection involved on the symbolic level in comparison to C+
+ where the "name" object can be used directly. I doubt however that
there is a way to involve the central iterator variable in the
operations and still get the same functionality. In fact, the
indirection gives an awful lot of flexibility sometimes even over a C+
+ iterator.

Such an iterator #define will be used most likely inside a function
with local identifiers present which act as the frame of reference of
the iterator. The name,start,end symbols are identifiers or
expressions which are available in the scope where the iterator is
intended to be used. E.g.

void frobnicate_all_tids(struct TaskQ *taskq, int n)
{
int ix;
int j;
/* access all tid's inside one task queue which currently
happens to be implemented as a fifo with head "hd"
and tail "tl" - the fifo itself is located inside one large
array together with the other fifos, each being limited
there with relating "top" and "bottom" indices */
#define queue_j ITER_DEF(ix,taskq[j].hd,taskq[j].tl, \
next( ix=(ix==taskq_limit[j].top)?
taskq_limit[j].bottom:ix+1, \
atend( ix==taskq[j].tl, \
deref( tid_queues[ix], \
ITER_ENDDEF))))
for(i=0;i<n;i++) {
ITER_INIT(queue_j);
while(!ITER_ATEND(queue_j)) {
frobnicate(ITER_GETN(queue_j));
}
}
#undef queue_j
}

The idea was to remove the awkward procedural work of accessing
awkward data structures out of such otherwise simple straightforward
algorithms. One can argue of course, that this is a failure in
encapsulation, but for the moment please just stay with me. As a plus,
the ITER_DEF macro is defined in such a way that one doesn't have to
supply a complete set of operations ( the shortest form would be
ITER_DEF(name,start,end,ITER_ENDDEF) ). A list of properties and
operations follows:

Basic Properties (must be supplied):
name...expression which is used to access (assign, dereference)
start...start expression, must evaluate to an assignable value
end...end expression, like start

Internal operations (can but don't have to be supplied):
[op. name] [default value] [explanation]
"TYPE" ... unsigned char ... type expression, element type
"DEREF" ... *name ... expression, read access (rhs)
"ASSIGN" ... *name ... expression, write access (lhs)
"ATEND" ... name >= end ... bool/int expression, end test
"INIT" ... name = start ... statement, move to start
"ENDEXN" ... {} ... statement, end exception (on
access)
"NEXT" ... ++name ... statement, move to next elem.
"PREV" ... --name ... statement, move to prev. elem.
"ADVANCE" ... name=+n_advance ... statement, move to n-th next elem.
"DISTANCE" ... end-name ... size_t expression, distance to end


Interface operations:
ITER_INIT // intitialization (name = start)
ITER_TYPE // iterator element type for defining temporaries
"ITER_TYPE(my_iter) tmp; tmp = ITER_GET(my_iter);"
ITER_GET // ...
ITER_PUT // ...
ITER_GETN // get and next
ITER_PUTN // put and next
ITER_ATEND // test on end
ITER_NEXT // advance one element
ITER_PREV // go back one element
ITER_UNGET // put back element
ITER_SKIP // advance more than 1 element

Although the facility works, I never dared to introduce or even
propose this to co-workers, mostly because they are frowning upon much
much (much) simpler preprocessor utilitzations. Where is your personal
pain threshold regarding meta-C aka preprocessor abuse ?

regards,
Mark

PS: yes, Boost has some nice facilities on their site (I think it's
called boost_pp or something) but I admit I never groked their method.
 
T

Thad Smith

Mark said:
Hi c.l.c,

after reading through a sufficient part of the iterator-thread, I
remembered an iterator-like preprocessing facility which I wrote a
while back. The aim was to provide zero-overhead ad-hoc iterator
functionality through the C preprocessor. I will roughly depict below
what I came up with: ....
an iterator definition looks like this:
#define pp_itername
ITER_DEF(name,start,end,DEREF(deref_exp,NEXT(next_stmt,ATEND(atend_exp,....ITER_ENDDEF)))))

The pp_itername will subsequently be used in operations, e.g.
ITER_NEXT(pp_itername)
x = ITER_GET(pp_itername) ....
The idea was to remove the awkward procedural work of accessing
awkward data structures out of such otherwise simple straightforward
algorithms. One can argue of course, that this is a failure in
encapsulation, but for the moment please just stay with me. As a plus,
the ITER_DEF macro is defined in such a way that one doesn't have to
supply a complete set of operations ( the shortest form would be
ITER_DEF(name,start,end,ITER_ENDDEF) ).
....
Although the facility works, I never dared to introduce or even
propose this to co-workers, mostly because they are frowning upon much
much (much) simpler preprocessor utilitzations. Where is your personal
pain threshold regarding meta-C aka preprocessor abuse ?

I think the fundamental tradeoff is complexity in definition vs. distributed
complexity without the iterator. In code used by multiple people, the use would
need to have a very high reward to justify the additional complexity of the
mechanism. The costs of using it are learning to use it properly and potential
difficulty in debugging. It may be warranted, especially if only one or two
iterators are defined, if it simplifies the main code to the point of
significantly improving clarity or reducing bugs.

I have done something similar (macros with multiple fields operated on by other
macros) for generating efficient I/O operations for embedded processors. I felt
that I hid the complexity within the framework and that with the documentation
using the macros was straight-forward. My application allowed me to define I/O
signal assignments (port, bit, polarity, number of bits) in one place and
generate efficient code by allowing the compiler to optimize constant expressions.
 
E

Ed Prochak

Hi c.l.c,

after reading through a sufficient part of the iterator-thread, I
remembered an iterator-like preprocessing facility which I wrote a
while back. The aim was to provide zero-overhead ad-hoc iterator
functionality through the C preprocessor. []


Although the facility works, I never dared to introduce or even
propose this to co-workers, mostly because they are frowning upon much
much (much) simpler preprocessor utilitzations. Where is your personal
pain threshold regarding meta-C aka preprocessor abuse ?

regards,
Mark

The question you have to ask yourself is:

Do I want to program in C or in some other language (like C++)?

These generic iterators seem to mask more of the programming. IMHO,
this is really an abuse of the preprocessor. If you want to create
another language, go ahead and write a decent compiler/interpreter for
it. But please don't bury C code under preprocessor macros like this.
I really do not see the benefit.

Ed
(Just one man's opinion.)
 
S

spinoza1111

after reading through a sufficient part of the iterator-thread, I
remembered an iterator-like preprocessing facility which I wrote a
while back. The aim was to provide zero-overhead ad-hoc iterator
functionality through the C preprocessor.
[]



Although the facility works, I never dared to introduce or even
propose this to co-workers, mostly because they are frowning upon much
much (much) simpler preprocessor utilitzations. Where is your personal
pain threshold regarding meta-C aka preprocessor abuse ?
regards,
Mark

The question you have to ask yourself is:

Do I want to program in C or in some other language (like C++)?

These generic iterators seem to mask more of the programming. IMHO,
this is really an abuse of the preprocessor. If you want to create
another language, go ahead and write a decent compiler/interpreter for
it. But please don't bury C code under preprocessor macros like this.
I really do not see the benefit.

  Ed
(Just one man's opinion.)

That's what they told Barney Stroustrup: the first version of C++ was
preprocessor-o-rama. "Hey Barney! This is a mess!"

The problem of resentment-reception (*Verstimmungsempfang*) of new
programming stylistics is isomorphic to what Theodore Adorno, the mid
20th century musicologist, philosopher, sociologist and all round nice
guy, called the "resentment listening" to the modern twelve tone
stylistics of Berg and Schoenberg.

That is, the bourgeois listener stalks out of Lulu saying das ist eine
gotterdamerung messenkampf, and claiming thereafter to be a devotee to
the Eternal (Ewige) as found in the harmonies of Bach 'n Beethoven, or
Mozart.

But if we examine Mein Herr's reception of Mozart we find him in
peaceful slumber or in the cafe of the hall watching the San Francisco
Giants, undt sagen, "why is always the marriage of god damned Figaro?
Now South Pacific, there was a show: singing! dancing! war!"

Singen! Tanzen! Der Krieg! But even the operetta is boring as our
attention span shrinken!

That is, for the same reason our burgher is unable to make sense out
of Berg he has little ability to note structure in Beethoven.

Resentment reading of programs is also isomorphic to the reception of
modern poetry, thought to be unique to modern poetry, but in actuality
likewise generalized. For the same people who don't read modern verse
struggle with Milton, and in fact the inability is rapidly becoming
strikingly generalized extending to the prestige university itself.
Stanley Fish, a literary critic, told the Chicago Tribune in 2000 that
he won't read a poem unless he's paid to do so.

What has this to do with programming, you may well ask.

It means that the claim that the program is "confusing" usually means
that it uses material in a creative, perhaps an effective, way, but
most corporate programmers today don't like to read jack shit.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,073
Latest member
DarinCeden

Latest Threads

Top