4

I'm trying to generate a function declaration using a macro

/* goal: generate int f(int a, float b) */
template<typename P>
struct ptype;

template<typename P>
struct ptype<void(P)> { typedef P type; };

#define NAMEe
#define COMMAe
#define COMMA ,

#define NAME(N) N PARAMS
#define PARAMS(P, ...) COMMA ## __VA_ARGS__ P NAME ## __VA_ARGS__
#define PARAM_ITER(P) P NAME

#define PROTO(R, N, P)  \
  ptype<void R>::type N (PARAM_ITER P (,e))

PROTO((int), f, (int)(a)(float)(b));

It will iteratively process the next (name) or (type) by NAME or PARAMS respectively, with the ... having an empty macro argument. But GCC complains with

prototype.hpp:20:35: warning: ISO C99 requires rest arguments to be used

And clang complains with

ptype<void (int)>::type f (int aprototype.hpp:20:1: warning: varargs argument missing, but tolerated as an extension [-pedantic]

I think this happens because of the following

#define FOO(X, ...)
FOO(A);

Because I'm not passing an argument for the ... or each of those (name) or (type). Is there any simple work around I can apply?


I've now used a technique similar to the technique used by @James to find the length of a parameter list. If as second argument, instead of O, ONT is passed, I will print the comma and NAME. The following is the final solution:

/* goal: generate void f(int a, float b) */
template<typename P>
struct ptype;

template<typename P>
struct ptype<void(P)> { typedef P type; };

#define TYPE_DO(X) X
#define TYPE_DONT(X)
#define TYPE_MAYBE(X, A, ...) TYPE_D ## A (X)

#define COMMA_DO ,
#define COMMA_DONT
#define COMMA_MAYBE(A, B, ...) COMMA_D ## B

#define NAME_DO NAME
#define NAME_DONT
#define NAME_MAYBE(A, B, ...) NAME_D ## B

#define NAME(N) N PARAMS
#define PARAMS(...) COMMA_MAYBE(__VA_ARGS__,O,O) TYPE_MAYBE(__VA_ARGS__,O,O) \
                    NAME_MAYBE(__VA_ARGS__,O,O)
#define PARAM_ITER(P) P NAME

#define PROTO(R, N, P)  \
  ptype<void R>::type N (PARAM_ITER P (D,ONT))

Test:

#define STR1(X) #X
#define STR(X) STR1(X)

int main() {
  // prints correctly
  std::cout << STR(PROTO((int), f, (int)(a)(float)(b)));
}

2 Answers 2

6

To solve the "FOO" problem, you can select different macros depending on the arity of the variable arguments pack. Here's a first shot at that:

// These need to be updated to handle more than three arguments:
#define PP_HAS_ARGS_IMPL2(_1, _2, _3, N, ...) N
#define PP_HAS_ARGS_SOURCE() MULTI, MULTI, ONE, ERROR

#define PP_HAS_ARGS_IMPL(...) PP_HAS_ARGS_IMPL2(__VA_ARGS__)
#define PP_HAS_ARGS(...)      PP_HAS_ARGS_IMPL(__VA_ARGS__, PP_HAS_ARGS_SOURCE())

#define FOO_ONE(x)     ONE_ARG:    x
#define FOO_MULTI(...) MULTI_ARG:  __VA_ARGS__

#define FOO_DISAMBIGUATE2(has_args, ...) FOO_ ## has_args (__VA_ARGS__)
#define FOO_DISAMBIGUATE(has_args, ...) FOO_DISAMBIGUATE2(has_args, __VA_ARGS__)
#define FOO(...) FOO_DISAMBIGUATE(PP_HAS_ARGS(__VA_ARGS__), __VA_ARGS__)

Usage example:

FOO(1)     // replaced by ONE_ARG:   1
FOO(1, 2)  // replaced by MULTI_ARG: 1, 2

(I'll try to revisit this to clean it up; I think there are definitely some unneccessary macros in there. I haven't had a chance to look into the broader problem that you describe, so I'm not sure if this solves that too. There may be a simpler way to solve that problem, too... I'm not particularly familiar with variadic macros. This preprocesses cleanly on mcpp.)

2

P99 has a macro that does exactly what you want, I think, namely P99_PROTOTYPE. It has a "signature" of

P99_PROTOTYPE(RT, NAME [, AT]*)

where RT is the return type (may be void) and AT are the argument types. The list of argument types may be empty, in which case it is substituted by void.

Beware that P99 is made for C99 and not for C++. You'll encounter particular difficulties if your arguments contain commas. C++'s syntax abuse of tokens < and > as bracketing expressions for templates is particularly bad for the preprocessor. C-preprocessor and C++ are basically incompatible languages on the syntax level.

P99 gets away from the difficulties that you are facing by detecting the number of arguments that the macro receives on a call and reacts differently on the border cases.

2
  • Yeah, the comma issue is actually why I put the return type into parentheses. So I could use the void P trick to get the type. I will look into P99_PROTOTYPE code and see how it does stuff. Thanks! Commented Mar 20, 2011 at 14:26
  • @Johannes, for the rare points that commas make difficulties in P99 I use P00_ROBUST to bracket the expression. This macro is just returning its argument list, but in the context of a macro expansion this has the advantage that the commas separating the arguments of P00_ROBUST are not counted for the arguments of the other macro. I am not sure if the C++ has the same evaluation order than the one of C99, so this might or might not work. Commented Mar 20, 2011 at 14:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.