How to eliminate a redundant macro parameter
Asked Answered
D

2

6

A while ago, I wrote a set of X-macros for a largish project. I needed to maintain coherent lists of both strings and enumerated references/hash values/callback functions etc. Here is what the function callback looks like

#define LREF_LOOKUP_TABLE_TEXT_SIZE 32
#define _LREF_ENUM_LIST(_prefix,_ref,...) _prefix ## _ ## _ref,
#define _LREF_BASE_STRUCT_ENTRY(_prefix,_ref) .text= #_ref "\0", .position= _LREF_ENUM_LIST(_prefix, _ref)
#define _LREF_FUNCTION_STRUCT_LIST(_prefix,_ref,...) {_LREF_BASE_STRUCT_ENTRY(_prefix,_ref) _prefix ## _ ## _ref ## _callback},

#define _LREF_ENUM_TYPEDEF(_prefix)                                               \ 
    typedef enum _prefix                                                          \  
    {                                                                             \  
        _ ## _prefix ## s(_prefix,_LREF_ENUM_LIST)                                \ 
        _LREF_ENUM_LIST(_prefix,tblEnd)                                           \ 
    } e_ ## _prefix

#define _LREF_LOOKUP_TABLE_TYPEDEF(_prefix, _extras)                              \ 
    typedef struct _prefix ## _lookup                                             \ 
    {                                                                             \ 
        const char text[LREF_LOOKUP_TABLE_TEXT_SIZE];                             \ 
        e_ ## _prefix position;                                                   \ 
        _extras                                                                   \ 
    } _prefix ##_lookup_t

#define LREF_GENERIC_LOOKUP_TABLE(_prefix, _type, _tabledef, _listdef, _extras)   \ 
    _LREF_ENUM_TYPEDEF(_prefix);                                                  \ 
    _LREF_LOOKUP_TABLE_TYPEDEF(_prefix,_tabledef);                                \ 
    _extras                                                                       \ 
    _LREF_LOOKUP_TABLE_DECLARATION(_prefix,_listdef, _type)

#define LREF_FUNCTION_LOOKUP_TABLE(_prefix, _type)                                \ 
    _ ## _prefix ## s(_prefix, _LREF_FUNCTION_DEF )                               \ 
    LREF_GENERIC_LOOKUP_TABLE(    _prefix,                                        \ 
        _type,                                                                    \ 
        void* (*function) (void*);,                                               \ 
    _LREF_FUNCTION_STRUCT_LIST,  )

This sits in a header file and allows me to write things like:

#define _cl_tags(x,_)         \
    _(x, command_list)        \
    _(x, command)             \
    _(x, parameter)           \
    _(x, fixed_parameter)     \
    _(x, parameter_group)     \
    _(x, group)               \ 
    _(x, map)                 \
    _(x, transform)

LREF_FUNCTION_LOOKUP_TABLE(cl_tag, static);

This keeps processing routines short. For example, loading a configuration file with the above tags is simply:

for (node_tag = cl_tag_lookup_table; node_tag->position != cl_tag_tblEnd; node_tag++)
{
    if (strcasecmp(test_string, node_tag->text) == 0)
    {
        func_return = node_tag->function((void*)m_parser);
    }
}

My question is this: I hate that I have to include the second parameter in my #define. I want to be able to write #define _cl_tags(_) instead of #define _cl_tags(x,_). As you can see, the x is only used to pass the prefix (cl_tag) down. But this is superfluous as the prefix is a parameter to the initial macro.

The solution to this would be easy if my preprocessor would expand the outer-most macros first. Unfortunately, GCC's preprocessor works through the inner-most macros, i.e. parameter values, before expanding the outermost macro.

I am using gcc 4.4.5


Clarification By C89 (and C99) standard, the following definitions

#define plus(x,y) add(y,x)
#define add(x,y) ((x)+(y))

with the invocation

plus(plus(a,b),c)

should yield

  1. plus(plus(a,b),c)
  2. add(c,plus(a,b))
  3. ((c)+(plus(a,b))
  4. ((c)+(add(b,a))
  5. ((c)+(((b)+(a))))

gcc 4.4.5 gives

  1. plus(plus(a,b),c)
  2. plus(add(b,a),c)
  3. plus(((b)+(a)),c)
  4. add(c,((b)+(a)))
  5. ((c)+(((b)+(a))))
Decuple answered 21/2, 2011 at 13:55 Comment(5)
"GCC's preprocessor works ..." according to the C Standard, which is very precise about macro expansion.Saxecoburggotha
Would that that were the case. To quote Harbison and Steele v.5 p.50 "Macro replacement is also not performed within the actual argument token strings of a functionlike macro at the time the macro call is being scanned." Compare that with GCC's CPP docs: "Macro arguments are completely macro-expanded before they are substituted into a macro body, unless they are stringified or pasted with other tokens. After substitution, the entire macro body, including the substituted arguments, is scanned again for macros to be expanded."Decuple
I was on X3J11 and worked through the development of the rules for macro expansion and I'm quite sure that gcc follows the Standard. The two texts are not inconsistent -- H&S is talking about (absence of) macro replacement during the scan of the arguments at the call site, whereas the gcc doc is talking about expansion within the body of the macro. If macro replacement were done in the arguments at the call site, then the exception for stringification or token pasting wouldn't be possible.Saxecoburggotha
P.S. Please see my PPCAT_NX and PPCAT macros below, which exemplify these rules and work on all conforming implementations.Saxecoburggotha
Your 'should yield' expansion is incorrect — it is not what the standard requires. The 'GCC gives' expansion is correct — it is what the standard requires.Lampe
S
1

Here's what I would do (have done similarly):

Put these in a utility header file:

/*
 * Concatenate preprocessor tokens A and B without expanding macro definitions
 * (however, if invoked from a macro, macro arguments are expanded).
 */
#define PPCAT_NX(A, B) A ## B

/*
 * Concatenate preprocessor tokens A and B after macro-expanding them.
 */
#define PPCAT(A, B) PPCAT_NX(A, B)

Then define this before including your LREF macro header file:

#define LREF_TAG cl_tag

Then, in your LREF macro header file,

#define LREF_PFX(x) PPCAT(LREF_TAG, x)
#define LREF_SFX(x) PPCAT(x, LREF_TAG)

Then replace every instance of _prefix ## foo with LREF_PFX(foo) and foo ## _prefix with LREF_SFX(foo).

(When pasting more than two tokens together, just use nested PPCAT's.)

Your invocation would become

#define LREF_TAG cl_tag
#define _cl_tags(_)        \
    _(command_list)        \
    _(command)             \
    _(parameter)           \
    _(fixed_parameter)     \
    _(parameter_group)     \
    _(group)               \ 
    _(map)                 \
    _(transform)

LREF_FUNCTION_LOOKUP_TABLE(static);
Saxecoburggotha answered 21/2, 2011 at 14:17 Comment(4)
Thanks but this trades one annoyance for a larger problem. Here, LREF_TAG will need to be defined prior to LREF_FUCTION_LOOKUP_TABLE (which, incidentally should retain both parameters) and then undef'd after the call in order to avoid collisions with future lookup tables.Decuple
@Decuple I can hardly see how that's a larger problem. And if the code is decently modularized, then there should only be one setting of LREF_TAG per file. As for "incidentally retain both parameters" -- not as I laid this out: all instances of _prefix in that macro would be replaced by LREF_TAG. But hey, if you don't want to do things this way, you're free to keep what you've got.Saxecoburggotha
I appreciate your reply and do not mean to denigrate it. What I meant by the above comment was that the #define LREF_TAG adds an additional constraint (the modularity that you describe). Ideally, the LREF_FUNCTION_LOOKUP_TABLE call should describe which lookup table it is creating so as to retain the self-documenting nature of the code.Decuple
@Decuple If you choose to pass cl_tag as an argument at one level, then you will have to pass it at every level. The only possible alternative would be to set a variable at some point, but there's no way to synthesize #define or any other preprocessor command. So the only way to set a variable is to do it manually, as in my answer. The bottom line is that it is pointless to ask your question if you reject the only conceivable solution.Saxecoburggotha
L
0

This answer just addresses the 'clarification'. Here is the correct behaviour:

#define plus(x,y) add(y,x)
#define add(x,y) ((x)+(y))

Initial:   plus(plus(a,b),c)
Pass 1a:   plus(add(b,a),c)
Pass 1b:   add(c,add(b,a))
Pass 2a:   add(c,((b)+(a)))
Pass 2b:   ((c)+(((b)+(a))))

The rules are that each macro is replaced once non-recursively (starting from the inner-most when they are nested); and then a new pass (aka. "rescan") happens repeating the same procedure, this continues until a pass performs no replacement.

I'm not sure what point you were trying to make though, as you give the same final conclusion for both GCC and what you expected to happen.

Lamm answered 10/3, 2016 at 3:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.