4

I'm wondering if it's possible to "nest" variadic macro invocations. I'm only truly concerned with GCC and Clang. My macro definition looks like this:

/**
 * @brief Invoke an instance method.
 */
#define $(obj, method, ...) \
    ({ \
        typeof(obj) _obj = obj; \
        _obj->interface->method(_obj, ## __VA_ARGS__); \
    })

I use this to conveniently call "instance methods" in my OO framework (https://github.com/jdolan/objectively):

$(array, addObject, obj);

Works boss. Unfortunately, I haven't yet figured out a way to allow nesting of these calls, which would be very useful in some situations; e.g.:

/**
 * @see MutableSetInterface::addObjectsFromArray(MutableSet *, const Array *)
 */
static void addObjectsFromArray(MutableSet *self, const Array *array) {

    if (array) {
        for (size_t i = 0; i < array->count; i++) {
            $(self, addObject, $(array, objectAtIndex, i));
        }
    }
}

The nested variadic macro invocation above fails to compile because the inner invocation is never expanded. Is it possible to fix this, or have I already abused the preprocessor to its limits? :)

jdolan
  • 580
  • 5
  • 14

1 Answers1

8

This is a common problem with nested preprocessor macros. Preprocessor expansion rules are fairly arcane; the relevant tl;dr is that macros are expanded in layers. The workaround is to add a layer of indirection in which the parameter can be expanded:

#define MI(obj, method, ...) \
  ({ \
    typeof(obj) _obj = obj; \
    _obj->interface->method(_obj, ## __VA_ARGS__); \
  })

#define M(obj, method, ...) MI(obj, method, __VA_ARGS__)

// This will now expand properly.
M(self, addObject, M(array, objectAtIndex, M(foo, bar, i)))

Side note: be aware that $ is not part of C's basic source character set; using it will probably not be portable.

Wintermute
  • 42,983
  • 5
  • 77
  • 80
  • `, ##` is not portable, either. – Jens Gustedt Dec 16 '14 at 21:42
  • If I understand your answer correctly, then I would only call `M` when I have arguments to pass. If I ever tried to call `M` without arguments beyond `method`, the resulting code will fail to parse. In other words, `M(self, methodName)` would not compile. Is this correct? Is there a way to have a single "entry point" that does the right thing, regardless of additional arguments? – jdolan Dec 16 '14 at 21:42
  • (And thanks, I understand that certain features I'm using here are non-standard. Like I mentioned, I'm only concerned with GCC and Clang.) – jdolan Dec 16 '14 at 21:44
  • Oh, I see. Yes, that appears to be necessary. I think that could be a gcc bug; I can't imagine that this behavior of the language extension is intended. – Wintermute Dec 16 '14 at 21:54
  • 1
    I don't actually think it's a bug; I think it's a limitation. The GCC manual says that "pasted tokens" (anything after `##`) are not expanded. So if you want the benefit of the "comma magic" that `##` brings, you forfeit argument expansion. It's a bummer. That said, your solution is certainly better than nothing. Thanks!! – jdolan Dec 16 '14 at 22:03
  • 1
    Could be. I was taken aback there for a minute though, I don't mind telling you. And you're welcome, of course. – Wintermute Dec 16 '14 at 22:04