Search code examples
c++c-preprocessorvariadic-macros

How to enhance this variable-dumping debug macro to be variadic?


First working code:

#include <iostream>

// NOTE: requires compiler which has __PRETTY_FUNCTION__, like gcc or clang
#define DUMP(v) std::cerr << __PRETTY_FUNCTION__ << ':' << __LINE__ << ':' << #v << "='" << (v) << "'\n"

int main(int argc) {

    DUMP(argc);
    DUMP(argc+1);
    return 0;
}

which gives stderr output (gcc):

int main(int):8:argc='1'
int main(int):9:argc+1='2'

Now I'd like to have variadic macro with any number of arguments, so for example

DUMP(argc, argc+1); would work, with output like:

int main(int):8:argc='1',argc+1='2'


But I could not yet come up with a nice solution. Is this even possible with a macro? If not, how about templates, or combination of macros and templates? C++11 and Boost are ok if needed, and solution can be gcc-specific if there's no standard way. Looking for actual code, which makes the variadic DUMP work like described above, or at least show equivalent info.


Solution

  • Ok. This is NOT NICE. But it works, up to a prespecified number of arguments (I got lazy at 4). It is heavily ripped off from here (seriously, click and upvote). It uses the trick of literally expanding the arguments in front of a prewritten list, pulling off the correct number from a fixed position at the back. It then calls the correct macro name by appending the number. Clear? Nope! Here's the code:

    #include <iostream>
    
    // Pretty normal macro tricks
    
    #define STRINGIZE(a) STRINGIZE1(a)
    #define STRINGIZE1(a) STRINGIZE2(a)
    #define STRINGIZE2(a) #a
    
    #define CONCAT(a, b)  CONCAT1(a, b)
    #define CONCAT1(a, b) CONCAT2(a, b)
    #define CONCAT2(a, b) a##b
    
    // Faux-recursively dump all the arguments
    
    #define DUMP_1(first) std::cout << STRINGIZE(first) << "='" << first << "'\n";
    
    #define DUMP_2(first, ...) \
    do {\
        std::cout << STRINGIZE(first) << "='" << first << "': ";\
        DUMP_1(__VA_ARGS__);\
    } while (false)
    
    #define DUMP_3(first, ...) \
    do {\
        std::cout << STRINGIZE(first) << "='" << first << "': ";\
        DUMP_2(__VA_ARGS__);\
    } while (false)
    
    #define DUMP_4(first, ...) \
    do {\
        std::cout << STRINGIZE(first) << "='" << first << "': ";\
        DUMP_3(__VA_ARGS__);\
    } while (false)
    
    
    // Count the arguments
    
    // Construct the forward/backward list:
    #define COUNT_ARGS(...)  COUNT_ARGS_(__VA_ARGS__, RSEQ())
    // Forward the list on (macro pain):
    #define COUNT_ARGS_(...) COUNT_ARGS_N(__VA_ARGS__)
    // The n+1th element is the count (predetermined to support up to n)
    #define COUNT_ARGS_N(_1, _2, _3, _4, N, ...) N
    #define RSEQ() 4, 3, 2, 1
    
    
    // This just calls the correct DUMP_#
    #define DUMP_(N, ...) CONCAT(DUMP_, N)(__VA_ARGS__)
    // Start the line, and start the "recursion"
    #define DUMP(...) \
    do {\
        std::cout << __PRETTY_FUNCTION__ << ':' << __LINE__ << ": "; \
        DUMP_(COUNT_ARGS(__VA_ARGS__), __VA_ARGS__); \
    } while (false)
    
    int main(int argc, char* argv[])
    {
        DUMP(argc);
    
        int i = 10;
        const char str[] = "Hello, world";
        DUMP(i, str);
    
        return 0;
    }
    

    Output:

    $ ./a.out 
    int main(int, char**):49: argc='1'
    int main(int, char**):52: i='10': str='Hello, world'
    

    Edit: Here's a simple version of the counting part (the hard part):

    #include <iostream>
    
    #define COUNT_ARGS(...)  COUNT_ARGS_(__VA_ARGS__, RSEQ())
    #define COUNT_ARGS_(...) COUNT_ARGS_N(__VA_ARGS__)
    #define COUNT_ARGS_N(_1, _2, _3, _4, _5, _6, N, ...) N
    #define RSEQ() 6, 5, 4, 3, 2, 1
    
    int main()
    {
        std::cout << COUNT_ARGS(a, b) << '\n';
        std::cout << COUNT_ARGS(a, b, c, d) << '\n';
    
        return 0;
    }
    

    Output:

    1
    2
    

    How does this work?

    Well say you set it up to count up to 6, as I have. It constructs a long list, and pulls off the 7th element. That always contains the correct value, because the list it pulls that value from is constructed by putting all the arguments the user gave, followed by a count in reverse.

    So by adding another argument, you push the backwards count along 1, and get a higher number from it. Look at this picture: enter image description here