Variadic macro to count number of arguments

The C-Preprocessor is a very powerful mechanism, which offers many different features. One of these features is called Variadic macros: macros that accept a varying number of arguments. It is interesting to note at this point, that such Variadic macros, despite being part of the C99 Standard, are not part of the C++ Standard at the moment. However, a big number of C++ compilers support it nevertheless.

While allowing the definition of Variadic macros, there is no built-in (preprocessor) way of obtaining the actual number of arguments that is passed to a specific Variadic macro. In this post we shall provide a possible macro implementation for such a query.

Let us first present the solution (which has also been proposed before), and then we shall move on to discuss how it works and its limitations;

#define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(__VA_ARGS__, 5,4,3,2,1)
#define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5,N,...) N

// to verify, run the preprocessor alone (g++ -E):
VA_NUM_ARGS(x,y,z)

As with every Variadic macro, __VA_ARGS__ is replaced with the passed arguments, therefore making a little “shift” in the arguments following it. Thus, when we check the arguments being passed to VA_NUM_ARGS_IMPL, the aforementioned “shift” causes the sixth argument to hold the right – initial – number of arguments.

The given implementation imposes a couple of limitations:

  • It will not work correctly for zero arguments. Some suggestions for an implementation capable of supporting this feature can be found in the comments section below.
  • It supports only a limited number of arguments (up to 5, to be precise). The macro can be extended to support a maximal number of up to 63 arguments (in a way that is pretty obvious), which seems to push the upper bound for number of preprocessor macro arguments any way.

Can you propose a case where this would be useful? I am planning to share a number of ideas in future posts.

21 thoughts on “Variadic macro to count number of arguments

  1. I can suggest a case where it is useful, as I have a problem I was just working on :)

    I have a macro designed to build a class which looks like (simplifying, the full thing is much nastier, here is the case for “2” arguments).

      #define BIG_CLASS2(name, Type1, Type2) 
      class name ## Box 
      {
        Type1 t1; 
        Type2 t2; 
        template <typename T1, typename T2>
        name##Box(T1 _t1, T2 _t2) : t1(_t1), t2(_t2) {} 
      };
    

    While I probably could write a macro that could produce this for any size, it’s tricky to get things like the “typename T1, typename T2″ to come out right, and I never need this bigger than 5, so I’m happy to just write them out.

    However, it is a bit annoying that I have to tell people to put the 1,2,.. at the start of the name, and will be nice to be able to remove it.

    1. Indeed, your example illustrates a great use case for the proposed construct. I was also thinking in these directions, and I think we could essentially make it a lot more general.

      Let us create the following macro dispatcher, which takes a macro name and its arguments, and generates the proper call:

      #define macro_dispatcher(func, ...) 
                  macro_dispatcher_(func, VA_NUM_ARGS(__VA_ARGS__))
      #define macro_dispatcher_(func, nargs) 
                  macro_dispatcher__(func, nargs)
      #define macro_dispatcher__(func, nargs) 
                  func ## nargs
      

      (It requires two more levels of indirection due to the nature of the preprocessor).

      Then, we could use it for “overloading” macros like you suggested. For example, here is how we would implement a MAX macro which accepts a varying number of arguments:

      #define max(...) macro_dispatcher(max, __VA_ARGS__)(__VA_ARGS__)
      
      #define max1(a) a
      #define max2(a,b) ((a)>(b)?(a):(b))
      #define max3(a,b,c) max2(max2(a,b),c)
      // ...
      
      // to verify, run the preprocessor alone (g++ -E):
      max(1,2,3);
      

      Then, we are able to implement a solution to your problem in the same way, without writing too much code:

      #define BIG_CLASS(...) 
                  macro_dispatcher(BIG_CLASS, __VA_ARGS__)(__VA_ARGS__)
      
      #define BIG_CLASS3 /* Your example */
      

      I think this is pretty handy, useful, and general at the same time.

      1. For max element you can just use Boost.Preprocessor:

        #include <boost/preprocessor/list/fold_left.hpp>
        #include <boost/preprocessor/selection/max.hpp>
        
        #define LIST (1, (3, (5, (2, (4, BOOST_PP_NIL)))))
        
        #define OP(d, state, x) BOOST_PP_MAX_D(d, state, x)
        
        #define LIST_MAX(list) BOOST_PP_LIST_FOLD_LEFT(OP, 0, LIST)
        
        LIST_MAX(LIST) // expands to 5
        
          1. I agree, varying number of arguments approach is interesting.

            I think Boost Preprocessor should starting using VA_ARGS since all popular compilers support this feature.

            Btw, your approach with max is not scalable.
            You have to define all maxN variations. :(

    2. Here you should use Boost.Preprocessor.
      It allows you what you need.

      On the other hand it seems that you invent Boost.Tuple :)

      1. No, don’t worry, I know about Boost.Tuple. For my example I was just simplifying out some details. In practice I am building a generic kind of Factory class. Internally I do actually make use of std::tuple and variadic templates, but that would just add more confusion!

        While we possibly could do these things with Boost.Preprocessor, I find it nicer to ask people to write:

        BUILD_FACTORY(Object, int, vector)

        Than:

        BUILD_FACTORY(Object, (int, (vector, (BOOST_PP_NIL))))

        Also, if I use Boost.Preprocessor, I find all the iteration functionality to be very nasty. Long ago I implemented tr1::tuple for g++, and started using boost.preprocessor. In the end I ended up reimplementing the bits of it I wanted, as it is very complex to read.

        Hopefully much of it, particularly to do with looping, could be reimplemented and simplified now we have variadic macros.

        1. You can use tuple in preprocessor:

          BUILD_FACTORY(Object, (int)(vector))

          It is nicer than second but uglier than first.
          On the other hand it works for all compilers.

          P.S.
          Talking about Factory pattern, are you familiar with auto-registering approach ?
          I mean that you don’t have manually add types to the factory. :)

  2. You can actually work around the zero-argument limitation by utilizing stringification:

    #define VA_NUM_ARGS(...) 
        (sizeof(#__VA_ARGS__) == sizeof("") 
         ? 0 : VA_NUM_ARGS_IMPL(__VA_ARGS__, 5,4,3,2,1))
    

    This will be evaluated as a constant expression by the compiler. Since the preprocessor removes all extra whitespace, an empty argument list will always become a zero-length string.

  3. Hi,

    thanks for this interesting post.

    However, the following code (as stated above) doesn’t work as expected in Visual Studio (I have tested on VS2010):

    #define VA_NUM_ARGS(…) VA_NUM_ARGS_IMPL(__VA_ARGS__, 5,4,3,2,1)
    #define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5,N,…) N

    // to verify, run the preprocessor alone (g++ -E):
    VA_NUM_ARGS(x,y,z)

    VS appears to have a bug regarding the __VA_ARGS__ expansion.

    Here is a workaround, hope it will help those who struggle with it in VS as I did:

    #define VA_NUM_ARGS(…) VA_NUM_ARGS_IMPL_((__VA_ARGS__, 5,4,3,2,1))
    #define VA_NUM_ARGS_IMPL_(tuple) VA_NUM_ARGS_IMPL tuple
    #define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5,N,…) N

    Best regards,
    Stan.

  4. Here is an implementation that works for the zero-arguments case, incorporating Jens Gustedt’s very helpful workaround posted further up in the comments. It could use some simplification, but it works:

    #define _ARG16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, …) _15
    #define HAS_COMMA(…) _ARG16(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)
    #define HAS_NO_COMMA(…) _ARG16(__VA_ARGS__, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)
    #define _TRIGGER_PARENTHESIS_(…) ,

    #define HAS_ZERO_OR_ONE_ARGS(…)
    _HAS_ZERO_OR_ONE_ARGS(
    /* test if there is just one argument, eventually an empty
    one */
    HAS_COMMA(__VA_ARGS__),
    /* test if _TRIGGER_PARENTHESIS_ together with the argument
    adds a comma */
    HAS_COMMA(_TRIGGER_PARENTHESIS_ __VA_ARGS__),
    /* test if the argument together with a parenthesis
    adds a comma */
    HAS_COMMA(__VA_ARGS__ (~)),
    /* test if placing it between _TRIGGER_PARENTHESIS_ and the
    parenthesis adds a comma */
    HAS_COMMA(_TRIGGER_PARENTHESIS_ __VA_ARGS__ (~))
    )

    #define PASTE5(_0, _1, _2, _3, _4) _0 ## _1 ## _2 ## _3 ## _4
    #define _HAS_ZERO_OR_ONE_ARGS(_0, _1, _2, _3) HAS_NO_COMMA(PASTE5(_IS_EMPTY_CASE_, _0, _1, _2, _3))
    #define _IS_EMPTY_CASE_0001 ,

    #define _VA0(…) HAS_ZERO_OR_ONE_ARGS(__VA_ARGS__)
    #define _VA1(…) HAS_ZERO_OR_ONE_ARGS(__VA_ARGS__)
    #define _VA2(…) 2
    #define _VA3(…) 3
    #define _VA4(…) 4
    #define _VA5(…) 5
    #define _VA6(…) 6
    #define _VA7(…) 7
    #define _VA8(…) 8
    #define _VA9(…) 9
    #define _VA10(…) 10
    #define _VA11(…) 11
    #define _VA12(…) 12
    #define _VA13(…) 13
    #define _VA14(…) 14
    #define _VA15(…) 15
    #define _VA16(…) 16

    #define VA_NUM_ARGS(…) VA_NUM_ARGS_IMPL(__VA_ARGS__, PP_RSEQ_N(__VA_ARGS__) )
    #define VA_NUM_ARGS_IMPL(…) VA_NUM_ARGS_N(__VA_ARGS__)

    #define VA_NUM_ARGS_N(
    _1, _2, _3, _4, _5, _6, _7, _8, _9,_10,
    _11,_12,_13,_14,_15,_16,N,…) N

    #define PP_RSEQ_N(…)
    _VA16(__VA_ARGS__),_VA15(__VA_ARGS__),_VA14(__VA_ARGS__),_VA13(__VA_ARGS__),
    _VA12(__VA_ARGS__),_VA11(__VA_ARGS__),_VA10(__VA_ARGS__), _VA9(__VA_ARGS__),
    _VA8(__VA_ARGS__),_VA7(__VA_ARGS__),_VA6(__VA_ARGS__),_VA5(__VA_ARGS__),
    _VA4(__VA_ARGS__),_VA3(__VA_ARGS__),_VA2(__VA_ARGS__),_VA1(__VA_ARGS__),
    _VA0(__VA_ARGS__)

    Enjoy
    Paul

  5. To support zero arguments:

    #define VA_NUM_ARGS(…) VA_NUM_ARGS_IMPL(, ##__VA_ARGS__, 5,4,3,2,1,0)
    #define VA_NUM_ARGS_IMPL(_0,_1,_2,_3,_4,_5,N,…) N

  6. I am using Microsoft visual studio 2010 and I faced a problem with _VA_ARGE_
    I used the solution provided by Stan
    —————————————————————————————–
    #define VA_NUM_ARGS(…) VA_NUM_ARGS_IMPL_((__VA_ARGS__, 5,4,3,2,1))
    #define VA_NUM_ARGS_IMPL_(tuple) VA_NUM_ARGS_IMPL tuple
    #define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5,N,…) N
    —————————————————————————————–
    it works very well but when I tired to apply the example provided by Rmn

    —————————————————————————————-
    #define macro_dispatcher(func, …) \
    macro_dispatcher_(func, VA_NUM_ARGS(__VA_ARGS__))
    #define macro_dispatcher_(func, nargs) \
    macro_dispatcher__(func, nargs)
    #define macro_dispatcher__(func, nargs) \
    func ## nargs

    #define max(…) macro_dispatcher(max, __VA_ARGS__)(__VA_ARGS__)

    #define max1(a) a
    #define max2(a,b) ((a)>(b)?(a):(b))
    #define max3(a,b,c) max2(max2(a,b),c)
    // …

    // to verify, run the preprocessor alone (g++ -E):
    max(1,2,3);
    ——————————————————————————————-
    if I called max(1,c);
    I got that error

    maxVA_NUM_ARGS_IMPL (1,c, 5,4,3,2,1) (1,c);

    1. I find an additional level of indirection helps microsoft.

      #define macro_dispatcher(macro, …) macro_dispatcher_(macro, VA_NARGS(__VA_ARGS__))
      #define macro_dispatcher_(macro, nargs) macro_dispatcher__(macro, nargs)
      #define macro_dispatcher__(macro, nargs) macro_dispatcher___(macro, nargs)
      #define macro_dispatcher___(macro, nargs) macro ## nargs

  7. Thanks for posting this!

    Regarding the first sourcecode line from the article:

    #define VA_NUM_ARGS(…) VA_NUM_ARGS_IMPL(__VA_ARGS__, 5,4,3,2,1)

    I think C99 requires at least one argument to be specified for __VA_ARGS__, when … is used in combination with named parameters. This means VA_NUM_ARGS() will fail when passed only a single parameter.

    However, it can be fixed by simply adding an extra param to the invocation of VA_NUM_ARGS_IMPL(), like this:

    #define VA_NUM_ARGS(…) VA_NUM_ARGS_IMPL(__VA_ARGS__, 5,4,3,2,1,unused)

    I chose to call it “unused” to indicate that it’s a meaningless filler.

  8. GCC (and Clang) both support a special extension for variadic macros; when placing “##” between a comma and __VA_ARGS__ then the comma is only part of the expansion if __VA_ARGS__ does not expand to “nothing”. If it does expand to nothing, the comma is removed.

    This extension allows you to easily change the macros to also report zero arguments correctly. Try the following in GCC or Clang:

    #define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(0, ## __VA_ARGS__, 5,4,3,2,1,0)
    #define VA_NUM_ARGS_IMPL(_0,_1,_2,_3,_4,_5,N,...) N

    VA_NUM_ARGS(x,y,z)
    VA_NUM_ARGS(x,y)
    VA_NUM_ARGS(x)
    VA_NUM_ARGS()

    It will print: 3, 2, 1, 0

  9. Problem Summary:
    In our project number of C++ application will run at
    Solaris and windows and use the same libaray code. Newly Log lvel tracing is introduced at Solaris but for that want to modify the common functions. This change is impactiting the windows app’s performance. Instead of ignoring the new changes at run-time want to check this at Static time (compile) time.

    Solution:

    enum LogTraceLevel { LOW, MEDIUM, HIGH, VERYHIGH };
    void displayMessageWindows(const char* message)
    {
    std::cout << "\n\t [ ] \t" << message << std::endl;
    }

    #define MACRO_VAR_NUM_ARGS_IMPL(_1,_2, N,…) N
    #define MACRO_VAR_NUM_ARGS_IMPL_(tuple) MACRO_VAR_NUM_ARGS_IMPL tuple

    #define displayMessageOne(var1) displayMessageWindows(var1)
    #define displayMessageTwo(var1, var2)

    #define displayMessage(…) MACRO_VAR_NUM_ARGS_IMPL_((__VA_ARGS__, displayMessageTwo(__VA_ARGS__), displayMessageOne(__VA_ARGS__)))

    void test3()
    {
    displayMessage("Lakshmana Maddineni");
    displayMessage("Lakshmana Kumar Maddineni", HIGH);
    }

    Now HIGH log displaymessages will not appearing at windows app's and this check is doing at compile time rather than runtime.

    http://stackoverflow.com/questions/3046889/optional-parameters-with-c-macros?answertab=votes#tab-top

    Thanks to Syphorlate

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>