开发者

Generating function declaration using a macro iteration

开发者 https://www.devze.com 2023-02-18 05:13 出处:网络
I\'m trying to generate a function declaration using a macro /* goal: generate int f(int a, float b) */

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开发者_StackOverflow));

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)));
}


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.)


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号