3

I've been aware of the VA_NARGS macro as described at C Preprocessor, Macro "Overloading" for a while, but I've always been put off by the large amount of boilerplate that it takes to make it work.

I recently had a need for this functionality, and so I gritted my teeth and wrote all the requisite macro code, in all its "glory."

In my particular case, I can consistently rely on all of the varargs arguments to be of a particular type. This led me to think that perhaps there's a better way, using sizeof and an array type. I tried this out on my local system, and it seems to work. However, I am concerned that this solution may be brittle (beyond the type restriction).

My questions are: Is this in fact a safe and reasonably sane solution to the problem? Or perhaps: What trouble am I asking for, down the line, if I use this? And finally: To the extent that there are problems with the attempt (below), are there tweaks that can be applied in order to salvage the general approach?

Here's the code, along with a demo main() function. In this case, the varargs arguments all have to be ints:

#include <stdio.h>

#define ARG_ARRAY(...) ((int[]) { __VA_ARGS__ })
#define ARG_COUNT(...) (sizeof (ARG_ARRAY(__VA_ARGS__)) / sizeof (int))

#define STUFF(label, ...) \
    stuff(label, ARG_COUNT(__VA_ARGS__), ARG_ARRAY(__VA_ARGS__))

void stuff(char *label, int count, int *values) {
    printf("[%s] count %d", label, count);

    for (int i = 0; i < count; i++) {
        printf("%s %d", (i == 0) ? ":" : ",", values[i]);
    }

    printf("\n");
}

int return1(void) {
    printf("Called `return1()`.\n");
    return 1;
}

int main(int argc, char **argv) {
    STUFF("blort");
    STUFF("frotz", return1());
    STUFF("fizmo", 2 + 3, 6 + 1);
    STUFF("glorf", 99, 999, 999);
    STUFF("igram", 9, 8, 7, 6, 5, 4, 3, 2, 1);
}

Here's a transcript:

[blort] count 0
Called `return1()`.
[frotz] count 1: 1
[fizmo] count 2: 5, 7
[glorf] count 3: 99, 999, 999
[igram] count 9: 9, 8, 7, 6, 5, 4, 3, 2, 1

The return1() printout is to verify that the function isn't called twice.


UPDATES:

It was pointed out in the comments that (int[]) { args } is C99 but not C++. In my case I can count on using a C compiler for the code in question, but it's still good to know this particular limitation.

It was pointed out in a now-deleted answer that C99 requires a varargs macro argument to be filled by at least one actual argument (though I think the spec is at best ambiguous in that regard). The compiler I have at hand (Clang on OS X) accepts the code with -Wall but does in fact complain with -pedantic, as @2501 aptly demonstrates in their comment.

-pedantic also complains about a zero size array (the no-args expansion (int[]) { }), though this could be fixed by always including a dummy element.

Community
  • 1
  • 1
danfuzz
  • 4,253
  • 24
  • 34

1 Answers1

1

If the macro arguments are all valid and non-void expressions, you could try and use decltype, like so:

#include <tuple>
#define NARGS(...) std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value

The macro arguments are not evaluated. For example:

#include <iostream>
#include <type_traits>

int main(int argc, char *argv[])
{
    std::cout << "Have "
              << NARGS("bar", 1, argc, 3, std::declval<int>())
              << " things\n";
}
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Thanks, though my question isn't about alternatives, of which there are several available (one of which I noted). Rather, I want to specifically know about problems with this particular proposed solution. Also FWIW, in my immediate situation I do have to compile with a C compiler, not C++. – danfuzz Oct 21 '14 at 23:17
  • 1
    @danfuzz: Why did you tag the question C++?! – Kerrek SB Oct 21 '14 at 23:18
  • I originally thought the proposed solution would apply to both C and C++. I'll un-tag. – danfuzz Oct 21 '14 at 23:18
  • @chris: Can you do that in a way that doesn't evaluate the arguments? – Kerrek SB Oct 21 '14 at 23:20
  • Fair point. I can't think of anything off the top of my head without manually attaching `decltype` to each of them. – chris Oct 21 '14 at 23:22