[comp.std.c] Variable arguments in macros

bradb@ai.toronto.edu (Brad Brown) (04/18/89)

I've been trying to find a way to have macros with variable numbers of
arguments in ANSI C, and I don't think it can be done.  I'm hoping that
I'm wrong and that someone can tell me how to do it.

What I'd like to do is have a header file with a macro similar to 

	#define trace(s)  { fprintf( stderr, s ); }

and then call it like

	trace( "Entering zot(), arg is %d", arg );

or

	trace( "I'm here." );

If I could get the whole argument list to bind to s then everything would 
work.  I can't do that, can I?

Thanks for your help!

					(-:  Brad Brown  :-)
					bradb@ai.toronto.edu

gwyn@smoke.BRL.MIL (Doug Gwyn) (04/18/89)

In article <89Apr17.163342edt.11027@ephemeral.ai.toronto.edu> bradb@ai.toronto.edu (Brad Brown) writes:
>I've been trying to find a way to have macros with variable numbers of
>arguments in ANSI C, and I don't think it can be done.

Not as such.

There is a trick that is sometimes convenient (but not in this
particular case):

#define FOO(x) bar x
FOO((a,b,c))	/* expands to: bar (a,b,c) */

>What I'd like to do is have a header file with a macro similar to 
>	#define trace(s)  { fprintf( stderr, s ); }

What I do for this is to define a variable-argument function that
invokes vfprintf() to do the printing.

rex@aussie.UUCP (Rex Jaeschke) (04/25/89)

> I've been trying to find a way to have macros with variable numbers of
> arguments in ANSI C, and I don't think it can be done.  I'm hoping that
> I'm wrong and that someone can tell me how to do it.

I have a solution but it isn't particularly elegant.

When I read your question I recalled working on related issues in 
X3J11 in a public comment review subgroup, re parens and commas as 
preprocessing tokens in macro calls.

First my (inelegant) solution then a discussion. This program works 
fine on WATCOM's V7 DOS compiler which, according to my test suite, is 
pretty darn close to X3J11's latest draft. It also works on Microsoft 
V5.1 but not Turbo C V2.

#include <stdio.h>

#define C ,

#define PR(s) fprintf(stderr, s)

main()
{
	PR("test1\n");
	PR("test2 %d\n" C 10);
	PR("test3 %d %d\n" C 10 C 100);
}

The output is:

test1
test2 10
test3 10 100

Obviously, the inelegant part is the use of the macro C. It's hard to 
call it anything meaningful yet not have it get in the way of 
readability.

According to the latest draft page 90 lines 36 and 38, and page 91 
line 4, the left and right parens and commas separating actual 
arguments (not those commas inside parens), MUST be preprocessing 
tokens. They CANNOT come into being as the result of macro expansion 
UNLESS as a result of the token pasting operator ##. As such, the C 
macro calls in my example are expanded to commas but the commas are all 
part of the one big argument to PR.

Aparently, numerous preprocessors used to (and probably still do) 
allow the parens and commas to be created by macro expansion. ANSI 
does not permit this.

Rex

----------------------------------------------------------------------------
Rex Jaeschke     | C Users Journal     |  Journal of C Language Translation
(703) 860-0091   | DEC PROFESSIONAL    |1810 Michael Faraday Drive, Suite 101
uunet!aussie!rex | Programmers Journal |     Reston, Virginia 22090, USA
----------------------------------------------------------------------------

leichter@CS.YALE.EDU (Jerry Leichter (LEICHTER-JERRY@CS.YALE.EDU)) (04/26/89)

I proposed a way to do this - and an additional reason why it would be useful
- a long time back, but I'm not sure just where.

Here's the jist:  Extend #define so that

#define f(a,b, ...)	<whatever>

is allowed.  When used as a macro, f must have at least two arguments, but
it may have any number of additional ones.  (The syntax is intended to be
exactly like that for functions with variable numbers of arguments.)

Within the definition of a macro taking a variable number of arguments, the
fixed arguments may be used as usual.  In addition, two built-in macros are
available:  _REST_ expands to the list of remaining arguments, including a
leading comma if there is at least one such argument; and _NARGS_ expands to
the total number of arguments passed.  Thus:

#define f(a,b, ...)	g(_NARGS_,a,b _REST_)
f(1,2,3,4)	==>	g(4,1,2 ,3,4)
f(1,2)		==>	g(2,1,2 )
f(1)		==>	<error>

For consistency, _REST_ and _NARGS_ should be available in any macro expan-
sion; _REST_ will be empty, and _NARGS_ will be the count of arguments (0 for
a macro taking no arguments).

(The ugliest part of this is the need to include the leading comma in _REST_,
and then omit it using _REST_ in an argument list.  If you don't do that,
however, you have to use:

#define f(a,b, ...)	g(_NARGS_,a,b,_REST_)
f(1,2)		==>	g(2,1,2,)

which is erroneous.  Of course, we could allow extraneous comma's in function
calls (and declarators, for consistency)....)

While this makes it easy to write macros calling fprintf and such, the main
reason it is nice is that it provides a portable, efficient way to get the
effect of the old nargs() call.  It's very useful to be able to determine how
many arguments you have been called with.  For example, it allows you to
extend a function by adding additional arguments without making previous uses
stop working.  It would also allow a function like fprintf, if it chose, to
check for the sanity of the arguments it was given - it would be useful to
have a debugging version of fprintf which told you that you hadn't matched
the number of arguments and the number of specifiers.

The objection that's been raised to requiring an nargs() facility is that it
can be expensive on some architectures and most functions don't need it.  With
the macro facility I'm proposing, you can add the support yourself for exactly
those functions where it's important to you.

							-- Jerry

karl@haddock.ima.isc.com (Karl Heuer) (04/28/89)

In article <58345@yale-celray.yale.UUCP> leichter@CS.YALE.EDU (Jerry Leichter (LEICHTER-JERRY@CS.YALE.EDU)) writes:
>[allow an ellipsis in the macro parameters, and have a builtin macro _REST_
>which] expands to the list of remaining arguments, including a leading comma
>if there is at least one such argument.

Not good enough.  Consider:
	#define f(a,b,...) (g(a,b),h(_REST_))
in which, for syntactic correctness, _REST_ must exclude the leading comma.
I suspect this was the major reason that variadic macros didn't make it into
the Standard: there was no clean proposal that handled the case with zero
additional args.

Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint

leichter@CS.YALE.EDU (Jerry Leichter) (05/02/89)

In article <12839@haddock.ima.isc.com>, (Karl Heuer) writes...
>In article <58345@yale-celray.yale.UUCP> (Jerry Leichter) writes:
>>[allow an ellipsis in the macro parameters, and have a builtin macro _REST_
>>which] expands to the list of remaining arguments, including a leading comma
>>if there is at least one such argument.
> 
>Not good enough.  Consider:
>	#define f(a,b,...) (g(a,b),h(_REST_))
>in which, for syntactic correctness, _REST_ must exclude the leading comma.
>I suspect this was the major reason that variadic macros didn't make it into
>the Standard: there was no clean proposal that handled the case with zero
>additional args.

There are two answers to this complaint:

a)  So what?  What you've shown is that the facility I'm proposing is not as
general as it might be.  That's true of many parts of C's macro facility:  It
has enough power to do certain simple jobs, but no more.

b)  Actually, however, a detailed analysis shows that your example is wrong
anyway.  Look more closely at h.  Whether h is a function or a macro, it is
impossible for it to be correctlly callable with either no arguments OR with
at least one argument.  Both ANSI C "variadic" functions, and the "variadic"
macros that I'm proposing, MUST be called with at least one argument.  Hence,
correct uses of f MUST have at least three arguments, and we can simply
rewrite f as:

	#define f(a,b,c,...) (g(a,b),h(c _REST_))

- a formulation which has the additional advantage of generating more consis-
tent and meaningful error messages, always reporting that it is f, which the
programmer wrote, which was called with the wrong number of arguments, rather
than a function h he may never have heard of.

This argument generalizes:  If you use _REST_ in specifying the arguments to a
macro or function, the only problem you can run into is if _REST_ comes first,
just after the "(".  But then it cannot be the whole argument list, by the
reasoning which applied to h.  So you CAN construct examples which fail, like:

	#define f(a,b,...) (g(a,b),h(_REST_,NULL))

If this really worries you, I claim it shouldn't - you've defined the wrong
calling convention for h.  It would be much better to re-define h so that

	#define f(a,b,...) (g(a,b),h(_NARGS_ _REST_))

is the right calling sequence.

If you look at all places in C where a comma makes sense, you can quickly find
all the possible problems:

1.  Comma operator:  If you wanted:

	a = (_REST_)

just write

	a = (0 _REST_)

instead.

2.  Function and macro argument lists - I've already discussed this.

3.  Initializer lists.  Again, you can probably construct plausible examples,
most of which have plausible workarounds.

The facility I'm proposing is simple and useful.  Sure, you can add bells and
whistles - just having both _REST_ (with leading comma) and _NREST_ (no lead-
ing comma) will do it.  (Or do you also need _EREST_, with the comma at the
end?)  Or you could add a pre-processor ?: operator - I might be able to write
stuff like:

	#define f(a,b,...) (g(a,b),h((_NARGS_ > 0) #? (_REST_) #: (,) NULL))

(This might be better done as an ifthenelse pre-defined macro.)  But all this
goes way beyond what's needed to solve a significant problem which is not
currently solvable in a clean way - as the discussion of the 2- vs. 3-argument
BSD open() makes clear.
							-- Jerry