1

I have the following code:

#define FOO_BAR x
#define FOO(x) FOO_BAR

I do want FOO(2) to expand to 2, but I'm getting x instead. I tried to use EXPAND macro to force extra scan:

#define FOO_BAR x
#define EXPAND(x) x
#define FOO(x) EXPAND(FOO_BAR)

Note, this is intentional, that FOO_BAR doesn't accept x as an argument. Basically, I cannot pass x to FOO_BAR.

But it doesn't work as well. Any ideas?

I want this to work on any compiler (MSVC, gcc, clang).

What exactly I am trying to accomplish

My end goal is to create type safe enums for OpenGL. So, I need to do mapping from my safe enum to unsafe ones. So I have something like:

enum class my_enum {
    foo,
    bar
}

GLenum my_enum2gl(my_enum e) {
    switch (e) {
    case my_enum::foo: return GL_FOO;
    case my_enum::bar: return GL_BAR;
    }
    return GL_NONE;
}

Since I'm lazy, I did some preprocessor magic. And implemented this as:

#define PP_IMPL_ENUM_VALUE(enum_pair) __PP_EVAL(__PP_IMPL_ENUM_VALUE enum_pair) 
#define __PP_IMPL_ENUM_VALUE(cpp_enum, gl_enum) cpp_enum,

#define PP_IMPL_CONVERT(enum_pair) __PP_EVAL(__PP_IMPL_CONVERT enum_pair) 
#define __PP_IMPL_CONVERT(cpp_enum, gl_enum) case name::cpp_enum: return gl_enum;

#define DEF_STATE_ENUM(name, ...)                       \
    enum name {                                         \
        PP_FOR_EACH(PP_IMPL_ENUM_VALUE, ##__VA_ARGS__)  \
    };                                                  \
    namespace detail {                                  \
    GLenum name ## 2gl(name e) {                        \
        switch(e) {                                     \
            __PP_EVAL(PP_FOR_EACH(PP_IMPL_CONVERT, ##__VA_ARGS__)) \
        default:                                        \
            assert(!"Unknown value");                   \
            return GL_NONE;                             \
        }                                               \
    }                                                   \
}

DEF_STATE_ENUM(my_enum,
    (foo, GL_FOO),
    (bar, GL_BAR)
)

The problem is that __PP_IMPL_CONVERT uses name which is not expanded. Passing x to FOO_BAR would mean that I'm passing some extra parameter to a functor for PP_FOR_EACH.

ivaigult
  • 6,198
  • 5
  • 38
  • 66
  • The only thing I can suggest is to use some external preprocessor. You can't do this within C. – Tom Karzes Oct 13 '17 at 13:42
  • You are getting x because that's what you defined FOO_BAR to be. You cannot expand a parameterless macro to a value that you want to pass in as a parameter. – mnistic Oct 13 '17 at 13:43
  • Additional scan won't help. How would it know that `x` should be `2`? There is no macro called `x`. – Eugene Sh. Oct 13 '17 at 13:44
  • 1
    C != C++. Tag only with the language that you're using, unless both are actually relevant. – tambre Oct 13 '17 at 13:45
  • @Eugene Sh, `FOO_BAR` is replaced with `x` and `x` is replaced with argument value. This is what I want. – ivaigult Oct 13 '17 at 13:45
  • 1
    @tambre, both are relevant, and I'm pretty sure any answer can be applied to both languages. – ivaigult Oct 13 '17 at 13:47
  • Why would it be replaced by argument value on the next pass? There are no arguments on the next pass. You want `FOO(x)` to be replaced by `x`? Then just `#define FOO(x) x`. – Eugene Sh. Oct 13 '17 at 13:47
  • What exactly are you trying to accomplish that requires this functionality? – Christian Gibbons Oct 13 '17 at 13:47
  • @ChristianGibbons sure, I will explain. Wait, I'll edit my question. – ivaigult Oct 13 '17 at 13:48
  • 1
    Your question is asking how to do something that is not possible to do; `x` in `FOO` is a parameter (expanded during argument substitution), `x` in `FOO_BAR` is _just_ `x`, and the two will never mix. Boost preprocessor's `FOR_EACH` constructs each take a data argument that's ideal for this use case. If you had a `PP_FOR_EACH` variant that did _that_, you could use it here. Are you willing to just use boost preprocessor library? – H Walters Oct 13 '17 at 14:18
  • `BOOST_PP_SEQ_FOR_EACH` seems to solve this. But I still hope that extra scan is possible. – ivaigult Oct 13 '17 at 14:21
  • If nobody else does by the time I get a spare breath, I can post an answer that explains why you can't do this with a preprocessor, gnu or MSVS or otherwise. – H Walters Oct 13 '17 at 14:30
  • I've written the C preprocessor some time ago and I do not think that additional scan for parameters in macro body is possible. In your particular case I'd suggest to use X-macros: https://stackoverflow.com/questions/264269/what-is-a-good-reference-documenting-patterns-of-use-of-x-macros-in-c-or-possib . It is far more convenient and readable than any other preprocessor magic. – Marian Oct 13 '17 at 14:30
  • @Marian I personally do like XMacro, but it's overkill for me to have a header file per every enum declaration. – ivaigult Oct 13 '17 at 14:32
  • Another possibility besides X-Macros per se would be to use a similar technique specific for the name part (`#define THIS_ENUM_NAME foo`/`DEF_STATE_ENUM((foo,GL_FOO),(bar,GL_BAR))`/`#undef THIS_ENUM_NAME` and use `THIS_ENUM_NAME`). I'd rather sketch out or point to (or see someone else do the same) solutions that could work than just expose on why a particular thing's impossible. FYI, you don't need a header file to implement X-macros; simply a macro expanding to calls suffices. – H Walters Oct 13 '17 at 14:36
  • glue includes a conversion utility for going between strongly typed enums and OpenGL enums: https://github.com/M-Mueller/glue/blob/master/include/glue/GlInternal.h Either way, you would be better off writing a Python script or something to auto-generate the conversion code for you than going through this macro magic – mnistic Oct 13 '17 at 14:38

1 Answers1

4

You need to understand

  1. The preprocessor fully expands the arguments to each function-like macro before substituting them into the macro's expansion, except where they are operands of the # or ## preprocessing operators (in which case they are not expanded at all).

  2. After modifying the input preprocessing token sequence by performing a macro expansion, the preprocessor automatically rescans the result for further macro expansions to perform.

In your lead example, then, given

#define FOO_BAR x
#define FOO(x) FOO_BAR

and a macro invocation

FOO(2)

, the preprocessor first macro expands the argument 2, leaving it unchanged, then replaces the macro call with its expansion. Since the expansion does not, in fact, use the argument in the first place, the initial result is

FOO_BAR

The preprocessor then rescans that, recognizes FOO_BAR as the identifier of an object-like macro, and replaces it with its expansion, yielding

x

, as you observed. This is the normal and expected behavior of a conforming C preprocessor, and to the best of my knowledge, C++ has equivalent specifications for its preprocessor.

Inserting an EXPAND() macro call does not help, because the problem is not failure to expand macros, but rather the time and context of macro expansion. Ultimately, it should not be all that surprising that when the replacement text of macro FOO(x) does not use the macro parameter x, the actual argument associated with that parameter has no effect on the result of the expansion.

I cannot fully address your real-world code on account of the fact that it depends centrally on a macro PP_FOR_EACH() whose definition you do not provide. Presumably that macro's name conveys the gist, but as you can see, the details matter. However, if you in fact understand how your PP_FOR_EACH macro actually works, then I bet you could come up with a variant that accepts an additional leading argument, by which you could convey (the same) name to each expansion.

Alternatively, this is the kind of problem for which X Macros were invented. I see that that alternative has already been raised in comments. You might even be able -- with some care -- to build a solution that uses X macros inside, so as to preserve the top-level interface you now have.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157