I am trying to swap the first two parameters of a variadic macro where
the list of parameters is unknown. I just know that at least 2
parameters will be provided. So the correct result should be:
VA_SWAP12(a, b) -> b, a
VA_SWAP12(a, b, c) -> b, a, c
VA_SWAP12(a, b, c, d) -> b, a, c, d
etc...
while the C99 expected result is:
VA_SWAP12(a, b) -> b, a,
VA_SWAP12(a, b, c) -> b, a, c
VA_SWAP12(a, b, c, d) -> b, a, c, d
The trailing comma in the case of only two parameters *is the problem*
and should not be there to make the swap working properly (reusability
of the result).
So when C99 added variadic macros it specifies explicitly that
(trailing) comma *are* included in the sequence of preprocessing tokens
held by __VA_ARGS__. Not a bad thing by itself. But C99 *forgot* to
provide tools to remove this trailing comma when it is followed by an
*empty* __VA_ARGS__.
I do not understand why C99 forgot (explicitly?) this point since it was
a well known problem for a long time? (GCC has an extension)
I am facing this problem nearly every time I rely on variadic macros
(except for simple school-exercice like the printf example). The only
(bad) solution I found is to provide two macros: VA_SWAP(a, b) and
VA_SWAPV(a, b, ...) and the user must select the right one.
Did I miss any tricks or is it just a foolish forgetting which makes me
the life harder?
a+, ld.
.....
Macro implementation example for the second set of results:
#define VA_SWAP12(...) \
VA_P2(__VA_ARGS__), VA_P1(__VA_ARGS__), VA_P2_(__VA_ARGS__)
#define VA_P1( p1, ...) p1
#define VA_P1_(p1, ...) __VA_ARGS__
#define VA_P2( p1, ...) VA_P1 (__VA_ARGS__)
#define VA_P2_(p1, ...) VA_P1_(__VA_ARGS__)
It says that the *variable* list (just the part matching matching
the ...) includes its *separating* commas.
> #define VA_SWAP12(...) \
> VA_P2(__VA_ARGS__), VA_P1(__VA_ARGS__), VA_P2_(__VA_ARGS__)
There are always at least two commas in the expansion because
you have explicitly put them there, in the text of VA_SWAP12.
> #define VA_P1( p1, ...) p1
> #define VA_P1_(p1, ...) __VA_ARGS__
> #define VA_P2( p1, ...) VA_P1 (__VA_ARGS__)
> #define VA_P2_(p1, ...) VA_P1_(__VA_ARGS__)
VA_SWAP(a, b):
VA_P2(a, b), VA_P1(a, b), VA_P2_(a, b)
VA_P1 (b), a, VA_P1_(b)
^ illegal, a, ^ illegal
Sun C 5.5 issues warnings for the argument mismatches.
It is difficult if not impossible to achieve precisely what you
want to do, because the C preprocessor does not pretend to be a
general, fully programmable macro processor like M4. There are
numerous restrictions in its capabilities; for example, one
cannot pass a comma as a macro argument. One could add many ad-
hoc features to increase its capabilities, but we chose not to.
> I am trying to swap the first two parameters of a variadic macro where
> the list of parameters is unknown. I just know that at least 2
> parameters will be provided. So the correct result should be:
The following will work on a compliant preprocessor (such as gcc):
#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__
#define SPLIT(i, ...) PRIMITIVE_CAT(SPLIT_, i)(__VA_ARGS__)
#define SPLIT_0(a, ...) a
#define SPLIT_1(a, ...) __VA_ARGS__
#define IS_3(...) \
SPLIT(0, SPLIT(1, \
IS_3_B(__VA_ARGS__, IS_3_A, IS_3_A, IS_3_A), \
1, 1 \
)) \
/**/
#define IS_3_A 0, 0
#define IS_3_B(a, b, _, ...) _
IS_3(a) // 0
IS_3(a, b) // 0
IS_3(a, b, c) // 1
IS_3(a, b, c, d) // 1
#define SWAP(a, ...) \
CAT(SWAP_, IS_3(a, __VA_ARGS__))(a, __VA_ARGS__) \
/**/
#define SWAP_0(a, b) b, a
#define SWAP_1(a, b, ...) b, a, __VA_ARGS__
SWAP(x, y) // y, x
SWAP(x, y, z) // y, x, z
Regards,
Paul Mensonides
> The following will work on a compliant preprocessor (such as gcc):
Of course, so will this:
#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__
#define SPLIT(i, ...) PRIMITIVE_CAT(SPLIT_, i)(__VA_ARGS__)
#define SPLIT_0(a, ...) a
#define SPLIT_1(a, ...) __VA_ARGS__
#define REM(...) __VA_ARGS__
#define REM_CTOR(tuple) REM tuple
#define RPAREN() )
#define SWAP(a, ...) SWAP_A(a RPAREN(), __VA_ARGS__ RPAREN(),)
#define SWAP_A(a, b, ...) \
REM_CTOR(SPLIT(0, SWAP_B((b, a, __VA_ARGS__)) \
/**/
#define SWAP_B(pair, ...) (REM pair, __VA_ARGS__)
Right. It is just an example. The underlying problem is to remove the
trailing comma if __VA_ARGS__ is empty.
>>#define VA_P1( p1, ...) p1
>>#define VA_P1_(p1, ...) __VA_ARGS__
>>#define VA_P2( p1, ...) VA_P1 (__VA_ARGS__)
>>#define VA_P2_(p1, ...) VA_P1_(__VA_ARGS__)
>
>
> VA_SWAP(a, b):
> VA_P2(a, b), VA_P1(a, b), VA_P2_(a, b)
> VA_P1 (b), a, VA_P1_(b)
> ^ illegal, a, ^ illegal
> Sun C 5.5 issues warnings for the argument mismatches.
>
> It is difficult if not impossible to achieve precisely what you
> want to do, because the C preprocessor does not pretend to be a
> general, fully programmable macro processor like M4. There are
> numerous restrictions in its capabilities; for example, one
> cannot pass a comma as a macro argument. One could add many ad-
> hoc features to increase its capabilities, but we chose not to.
I understand your point. But when you add a feature, it could be useful
to check if it is complete and what can be done with it, and if a simple
and easy-to-do extension could help in many common situations. For
example, the behavior of , ## __VA_ARGS__ of gcc when __VA_ARGS__ is
empty and follows a trailing comma could have been considered (if you
did it, why was it rejected?) or the possibility to have __VA_NUM_ARGS__
to know the number of arguments held by __VA_ARGS__ would also have
helped. If you had solutions to emulate these behaviors when the norm
was written (discarding these extensions), some examples in the norm
would have been very useful.
My position is not to ask for more features, but just to ensure that a
feature is complete. It is like thinking to add a variadic function
(e.g. scanf) to the standard library without thinking to its pending 'v'
version using a va_list (e.g. vscanf). It cost nothing to thing about it
and make it reusable.
Regards,
ld.
Thanks for the tricks. It took me half an hour to understand the
subtilities ;-)
I simplified your macros and use them as a kind of ?: macro operator
based on the number of arguments >1 included in __VA_ARGS__:
#define VA_PN(i, ...) VA_P##i(__VA_ARGS__)
#define VA_P0(a, ...) a
#define VA_P1(a, ...) __VA_ARGS__
#define VA_DO2P(a) a, a
#define VA_IF2P(a, b, ...) b
#define VA_HAS2P(t,f, ...) \
VA_PN(0, VA_PN(1, VA_IF2P(__VA_ARGS__, VA_DO2P(f)), t))
// usage:
VA_HAS2P(yes,no, ) // no
VA_HAS2P(yes,no, a) // no
VA_HAS2P(yes,no, a, b) // yes
VA_HAS2P(yes,no, a, b, c) // yes
Unfortunately, I did not succeeded to go down to VA_HAS1P since it seems
that there is no way to make the difference between no parameter and one
parameter. If I add an 'artificially' and extra parameter, I
automatically come back to the case of 2+ paramaters, never less.
Thanks again!
Kind Regards,
ld.
Clearly the direct application of the simple meaning of
## would *require* that the result consist of a comma
in that case. Requiring anything else would have been
an ad-hoc kludge based on a presupposition of the usage.
While that might have helped in your specific usage, it
might have been an unpleasant surprise for others. As
a general principle we tried to keep the specs simple
and avoid such kludges.
> Thanks for the tricks. It took me half an hour to understand the
> subtilities ;-)
:)
> I simplified your macros and use them as a kind of ?: macro operator
> based on the number of arguments >1 included in __VA_ARGS__:
[snip]
> Unfortunately, I did not succeeded to go down to VA_HAS1P since it
> seems that there is no way to make the difference between no
> parameter and one parameter.
It is impossible to detect emptiness in general (namely because you cannot
concatenate two arbitrary preprocessing tokens together). However, you can can
very close in a variety of ways if you restrict certain kinds of input. For
example, the following is an emptiness detection macro that prohibits input that
ends in a macro name that refers to a function-like macro:
// standard concatenation macros.
#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__
// binary intermediate split macro.
//
// An "intermediate" is a single macro argument
// that expands to more than one argument before
// it can be passed to another macro. E.g.
//
// #define IM x, y
//
// SPLIT(0, IM) // x
// SPLIT(1, IM) // y
#define SPLIT(i, ...) PRIMITIVE_CAT(SPLIT_, i)(__VA_ARGS__)
#define SPLIT_0(a, ...) a
#define SPLIT_1(a, ...) __VA_ARGS__
// parenthetic expression detection on
// parenthetic expressions of any arity
// (hence the name 'variadic'). E.g.
//
// IS_VARIADIC(+) // 0
// IS_VARIADIC(()) // 1
// IS_VARIADIC(text) // 0
// IS_VARIADIC((a, b, c)) // 1
#define IS_VARIADIC(...) \
SPLIT(0, CAT(IS_VARIADIC_R_, IS_VARIADIC_C __VA_ARGS__)) \
/**/
#define IS_VARIADIC_C(...) 1
#define IS_VARIADIC_R_1 1,
#define IS_VARIADIC_R_IS_VARIADIC_C 0,
// lazy 'if' construct.
// 'bit' must be 0 or 1 (i.e. Boolean). E.g.
//
// IIF(0)(T, F) // F
// IIF(1)(T, F) // T
#define IIF(bit) PRIMITIVE_CAT(IIF_, bit)
#define IIF_0(t, ...) __VA_ARGS__
#define IIF_1(t, ...) t
// emptiness detection macro...
#define IS_EMPTY_NON_FUNCTION(...) \
IIF(IS_VARIADIC(__VA_ARGS__))( \
0, \
IS_VARIADIC(IS_EMPTY_NON_FUNCTION_C __VA_ARGS__ ()) \
) \
/**/
#define IS_EMPTY_NON_FUNCTION_C() ()
#define EMPTY()
IS_EMPTY_NON_FUNCTION() // 1
IS_EMPTY_NON_FUNCTION(EMPTY()) // 1
IS_EMPTY_NON_FUNCTION(,) // 0
IS_EMPTY_NON_FUNCTION(+) // 0
Regards,
Paul Mensonides
I agree. The gcc extension is a total kludge. Besides which, we can already do
everything related to this except detect emptiness in the general case--the
general case excludes pathological input such as...
#define LPAREN (
IS_EMPTY( LPAREN )
(The pathological cases of half-open parentheses can never really be handled in
general.) In order to detect emptiness, we need well-defined semantics (rather
than undefined behavior) for token-pasting any two arbitrary tokens. E.g.
#define CAT(a, b) a ## b
CAT(+, -)
That is currently undefined behavior, but I would dearly love it if it wasn't.
(I don't really care if the result is a retokenization of the spellings of the
operands or whether it is a no-op if it doesn't form a single preprocessing
token--either way suits me fine.)
Regarding the counting of arguments in the zero or one case, the way that the
standard is defined follows the allowance for empty arguments (a great thing).
If the macro is defined as having a ... argument, then you cannot pass zero
arguments to the macro--you can only pass, at minimum, a single empty argument.
That is no different than...
#define A(x) // ...
A()
Treating the empty () as no arguments just doesn't scale in macro code. For
example, say I'm using macros to apply something to every variadic argument.
(I'll skip the code that will do that, but just say that, for variadic arguments
a, b, ... z, it generates F(a) F(b) ... F(z).) Further, say the algorithm that
does this doesn't immediately know how many arguments there are. Instead, it
simply pops off the first argument and continues on the tail (i.e. treating the
variadic arguments as a cons-style list). The problem is that (,,) is clearly
three arguments (it can be nothing else). Likewise, (,) is clearly two
arguments. However, when you pop the first argument of (,), you get (), which
is suddenly ambiguous unless you always treat () as a single empty
argument--with the lone special case of true nullary macro definitions. [As an
aside, empty arguments are not at all unlikely in macro code, so they can't
really be viewed as a border case. Emptiness has meaning in and of itself--at
the very least, as a lack of something else.]
In any case, sometimes an empty argument is not valid per the specification or
documentation of the macro itself. In those cases, it is reasonable for the
macro to consider an empty argument as a non-argument. For now, we can handle
that case for all macros where there is, conceptually, at least one named
parameter. The last named parameter can be converted to ..., and we can easily
tell the difference between one or more arguments (just not zero and one because
there is no syntactic distinction). That allows us to pass zero variadic
arguments in the conceptual sense.
Regards,
Paul Mensonides
This came up in a DR (and was argued to death in the
newsgroup). The response was that whether the () has
zero or one argument is resolved by referring to the
definition of the designated macro.
> ... empty arguments are not at all unlikely in macro code, so they can't
> really be viewed as a border case. Emptiness has meaning in and of itself--at
> the very least, as a lack of something else.]
Indeed, that's why I pushed so hard to have C99 support
empty macro arguments, including making them usable in
pasting and stringizing operations.
> ... That allows us to pass zero variadic
> arguments in the conceptual sense.
From the point of view of macro power, it is unfortunate
that C preprocessing doesn't have an NARGS macro and
enough programmability to select different macros to
expand based on numerical tests (at preprocessing time).
However, traditionally C's preprocessing has never been
meant as a complete macro programming language, and we
decided to keep it that way. It is a testament to human
ingenuity that some people can find ways around this
without resorting to some other macro processor.
And some of us felt that the standard doesn't state this clearly
enough. The issue was brought before the committee, which decided
that the existing text is clear enough.
As Doug says, this was argued to death; please see the archives before
(or instead of) rehashing the arguments.
--
Keith Thompson (The_Other_Keith) ks...@mib.org <http://www.ghoti.net/~kst>
San Diego Supercomputer Center <*> <http://users.sdsc.edu/~kst>
We must do something. This is something. Therefore, we must do this.
I remember, but that isn't what I'm referring to. I'm not talking about the
number of arguments in a macro invocation, but rather the argument list
"separated" from the invocation. As in, (a, b, c). We can tell, unambiguously,
that there is three arguments (or elements, or whatever). I don't mean that we
can do it reading the code, I mean that we can do it programmatically. We
cannot, however, programmatically differentiate between zero elements and one
empty element--and that is how it should be, IMO. I'd like to be able to detect
emptiness generally, but treating () as a nullary argument list
(programmatically) doesn't scale. The special case, rather, is nullary macro
definitions--which is fine. My point is only that, programmatically speaking,
the default interpretation of () should be as a unary argument list whose single
argument is empty.
I agree with you're response to the issue you refer to above. It is obvious
that the preprocessor must match the actual argument list to the arity of the
macro definition.
>> ... empty arguments are not at all unlikely in macro code, so they
>> can't really be viewed as a border case. Emptiness has meaning in
>> and of itself--at the very least, as a lack of something else.]
>
> Indeed, that's why I pushed so hard to have C99 support
> empty macro arguments, including making them usable in
> pasting and stringizing operations.
In that case, thank you. :)
>> ... That allows us to pass zero variadic
>> arguments in the conceptual sense.
>
> From the point of view of macro power, it is unfortunate
> that C preprocessing doesn't have an NARGS macro and
> enough programmability to select different macros to
> expand based on numerical tests (at preprocessing time).
The preprocessor _does_ have enough programmability to do that, but you have to
invoke it manually. It is possible to do it such that it is an O(1) operation
if you limit the maximum number of arguments. It is also possible to define it
such that it is an O(n mod f) operation (where n is the actual number of
arguments and f is an unrolling factor). An example definition of O(1) size
that limits the number of arguments to five is (but it can be trivially
extended)...
// general purpose components...
#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__
#define SPLIT(i, ...) \
PRIMITIVE_CAT(SPLIT_, i)(__VA_ARGS__) \
/**/
#define SPLIT_0(a, ...) a
#define SPLIT_1(a, ...) __VA_ARGS__
#define COMMA() ,
#define REM(...) __VA_ARGS__
// relatively-specific components...
#define SIZE(...) \
SPLIT(0, SPLIT(1, \
SIZE_A(COMMA, REM(__VA_ARGS__)),, \
)) \
/**/
#define SIZE_A(_, im) \
SIZE_B(im, _() 5, _() 4, _() 3, _() 2, _() 1,) \
/**/
#define SIZE_B(a, b, c, d, e, _, ...) _
#define OVERLOAD(prefix, ...) \
CAT(prefix, SIZE(__VA_ARGS__)) \
/**/
// sample usage...
#define MACRO_1(a) < a >
#define MACRO_2(a, b) < a > < b >
#define MACRO_3(a, b, c) < a > < b > < c >
#define MACRO(...) \
OVERLOAD(MACRO_, __VA_ARGS__)(__VA_ARGS__) \
/**/
MACRO(1) // < 1 >
MACRO(1, 2) // < 1 > < 2 >
MACRO(1, 2, 3) // < 1 > < 2 > < 3 >
> However, traditionally C's preprocessing has never been
> meant as a complete macro programming language, and we
> decided to keep it that way. It is a testament to human
> ingenuity that some people can find ways around this
> without resorting to some other macro processor.
Yes.
Regards,
Paul Mensonides
... it's like programming a Turing machine!
Heh heh. It isn't that bad. Concepts and facilities can be encapsulated behind
library interfaces. Those interfaces can be used by other library facilities as
well. Once those library facilities are in place, you can do quite a bit.
In the code that I've posted so far on this thread, very little of it is
specific to the particular task to which it is applied. In the O(1) arity
detection implementation in the previous message presents the following
interfaces: CAT, PRIMITIVE_CAT, SPLIT, COMMA, REM, SIZE, and OVERLOAD (all of
the rest are implementation details of the interfaces they accompany). Of
those, CAT, PRIMITIVE_CAT, SPLIT, COMMA, and REM are very general purpose
components that can be used for all sorts of things. SIZE is less general
purpose, but can still be used for things other than emulating overloading
(based on number of arguments). OVERLOAD is the only truly specific interface,
but it is still a reusable interface. Ideally, such a primitive exists in a
library (as well as all the others), and client code would just look like this
if they want to "overload" a macro on number of arguments:
#include "cpp/overload.h"
// interface...
#define MACRO(...) \
OVERLOAD(MACRO_, __VA_ARGS__)(__VA_ARGS__) \
/**/
// implementation...
#define MACRO_1(a) // ...
#define MACRO_2(a, b) // ...
#define MACRO_3(a, b, c) // ...
The point is that nearly all of the clever tricks required to implement these
things can be hidden from clients. That isn't to say it is ideal, because we
have to manually do what normal languages have built in support for. Because we
can hide most of the details behind library abstractions, the library and the
language becomes a new language. Or rather, the library extends the language.
This is not that dissimilar from other languages with "real" macro mechanisms
(i.e. Lisp, Scheme, etc.). In such languages, many basic language constructs
(such as loops) are implemented as library elements.
That is why I provided the OP with encapsulated primitives instead of
implementing SWAP directly as a standalone entity. It is better to have a
predicate like IS_3 than it is to implement the predication directly in the SWAP
implementation. (In fact, IS_3 is only a specific case of the must more general
predicate which takes the size to test for as an argument as well.)
Regards,
Paul Mensonides
>> This came up in a DR (and was argued to death in the
>> newsgroup). The response was that whether the () has
>> zero or one argument is resolved by referring to the
>> definition of the designated macro.
>
> And some of us felt that the standard doesn't state this clearly
> enough. The issue was brought before the committee, which decided
> that the existing text is clear enough.
>
> As Doug says, this was argued to death; please see the archives before
> (or instead of) rehashing the arguments.
Hi Keith, I'm not referring to the same issue, at least not directly. I'm
referring to, basically, counting the number of elements in a comma-separated
list of preprocessing token sequences. The handling of the zero-element vs.
empty-single-element case is analogous to the aforementioned discussion, except
that macro code itself cannot test the arity defined a macro definition. I'm
not arguing that it should be able to, BTW. I was merely commenting, probably
in an overly roundabout way, that () and (a, b, c) are specific cases of the
general form:
argument-list:
( argument-sequence )
argument-sequence:
argument
argument-sequence , argument
In other words, with empty arguments and placemarkers, the nullary macro
invocation becomes the special case, not the general case.
I agree with Doug that there is nothing that is difficult to understand in the
standard regarding nullary macro invocations and unary macro invocations with an
empty argument. (It has always been perfectly clear to me anyway.) The
preprocessor already has to look up identifiers in the symbol table to see if
they are macros. If they are, it has to look up whether they are object-like or
function-like (i.e. another instance of something depending on the definition of
the macro).
Regards,
Paul Mensonides
Yes, indeed I appreciate how nicely you have presented this.
One wants to be careful not to take it too far, however,
so that a "normal" C programmer can readily understand the
code when it is handed to him to maintain. You probably
recall Bourne's ALGOLization of C via macros, used in the
Bourne shell, ADB, and maybe one other standard Unix app.
Almost everybody complained about this, and eventually the
shell was rewritten in plain C (sort of a macro expansion).
Also many years ago one of my team members, who was very
smart technically but not sufficiently concerned with the
accessibility of his code to others, figured out a way to
code in C that looked very LISP-like. The rest of the
team wasn't able to make much use of his work, alas.
I agree with this up to a point. There is no substitute for documentation. Say
that OVERLOAD was part of some library and a maintainer comes across a use of
that macro in some code, it is reasonable that the maintainer look at the
documentation for OVERLOAD. If the maintainer is instead supposed to upgrade
(or otherwise maintain) the OVERLOAD macro itself, then he must be qualified to
do so, because the macro is written in Cpp, rather than the underlying C
language--i.e. mastery of C does not imply mastery of the C preprocessor. This
is not unlike anything else in the language. Maintainers of code typically need
to understand the problem domain which the code addresses. Furthermore, you
don't need the preprocessor to write cryptic or overly-complex code--you can, in
fact, do that in any language (and most people are capable of doing it without
even being taught). Dealing with complexity is the fundamental purpose of
abstraction--particularly the isolation and encapsulation of any necessary
complexity. Abstraction is the key to well-structured use of the preprocessor
as well.
> You probably
> recall Bourne's ALGOLization of C via macros, used in the
> Bourne shell, ADB, and maybe one other standard Unix app.
> Almost everybody complained about this, and eventually the
> shell was rewritten in plain C (sort of a macro expansion).
> Also many years ago one of my team members, who was very
> smart technically but not sufficiently concerned with the
> accessibility of his code to others, figured out a way to
> code in C that looked very LISP-like. The rest of the
> team wasn't able to make much use of his work, alas.
The purpose of preprocessor-based code such as that shown in this thread is not
to supplant C or C++ (it can't even get close in either case), nor is its
purpose to mangle C or C++ to look like some other language. The purpose of
this stuff is the automated generation of regular C and C++ code to avoid
external code generators and to decrease maintenance points.
In relation to what I said above about loops as library elements in Lisp (etc.),
I wasn't referring to using the preprocessor to supplant runtime loops. I was
referring to higher-order loop abstractions that extend the preprocessor such
that you can do other meaningful things with it (such as process a variadic list
of macro arguments) without re-emulating recursion or iteration every time.
These kinds of preprocessor tools aren't worthwhile in and of themselves; they
are worthwhile because they can be put together to build higher-level components
that do something worthwhile. (Yes, that implies that they can also be put
together to build higher-level components that don't do something worthwhile.
Like all other things, these kinds of tools can be used well and misused.)
A trivial example is the automated application of Duff's Device. The ideal
unrolling factor, for example, often changes from platform to platform (or from
compiler to compiler, or processor to processor). When the code moves to a
different environment, the code can be trivially changed by changing a single
number representing the unrolling factor (based on profiler results, of course).
That number can itself be a parametization that is specifiable on the command
line of the compilation or in a header that makes platform-specific decisions.
If so, then the code in question can change itself as needed. An important
point is that the code communicates the concept of a Duff's Device more clearly
than a manually-coded instance of it does because Duff's Device is a
implementation *pattern* not an implementation. The preprocessor, utilizing
abstraction, can generate concrete instances of Duff's Device. No other
construct in C or even C++ can do that (including templates).
With all of that said, there are many ways to misuse the preprocessor--even when
using it to generate code. Abstraction, generalization, and extensibility have
significant benefits, but they can also have drawbacks (such as performance).
There are such things as too much abstraction, too much generalization, and too
much extensibility. Too much of any of them makes a program more complex (and
more difficult to understand and maintain) rather than less. Sometimes the
benefits simply don't outweigh the drawbacks. Good code maintains a balance,
and achieving the ideal balance is really what programming is all about.
Regards,
Paul Mensonides