C++ How to simply handle variable number of function arguments

So little C++ so much good!

The term “beginner” is a misnomer in the context of C++. Becoming a solid C++ beginner takes time and effort.

[Published on: 2 August 2018]

[detour]

This kind of article for apparent “C++ beginners” is required for a few various reasons, and that is not a good situation for C++. And to understand some of the reasons that got us in that situation,  please read what the C++ creator (Bjarne Stroustrup) has written about endless language extending. I think a very important “message to all” C++ well-wishers under by now well-known name:  “Remember the Vasa!“.

[/detour]

This article is one of my attempts to show you that C++ code can be simple code.

C variable number of arguments, aka varargs

If and when the function can have “zero or more arguments” it is said to have a variable number of, arguments. Function well known to you can have one or more arguments. printf() .

This capability is inherited from C and exists as such for decades now.

[Update 2023 JAN] For C aficionados here is a tasty little macro to help with the code below. [/Update]

Before we proceed an important remark:

In standard C there is no mechanism that will reveal with how many arguments the variadic function has been called.

If that is not explained and beginner sees the implementation a confusion will arise.  Thus we need to further explain the only two ways to program around that are

  1. first int argument is number of variadic arguments
    1. example:  average(3, 1,2,3 ); , we inform there is 3 arguments
  2. last argument is “sentinel”: a special value that means “last argument is reached as the first before the sentinel”
    1. example: average(1,2,3, 0) ; , zero is the sentinel to mark the end of list of arguments

Other than those two designs, we might use macro with variadic number of arguments to call our average , with zero or more arguments, without the user needing to call it with sentinel or number of args argument. Example is ready here , please study later.

Bellow we use : first argument is number of variadic parameters, design.

C++ param packs

The whole program in godbolt: https://godbolt.org/z/7W6doYncj

Since C++11 became a standard, this was better formalized and the standard C++ has been equipped since then, to handle this in a bit more high-level way, with the help of templates too.

Since C++11, the variable set of arguments is called a “parameter pack”. For a good level of detail about it please see here.

Function average above can do only int’s and will return int, In this post, we are leaving C behind and diving into C++ function templates,  with the capability to receive any number of function arguments, so-called (scarily): Variadic function template.

Now I am sure since you are reading this you know already all this stuff. But you might think it is non-trivial to deal with C++ parameter packs and as such, you are avoiding them or blindly copy-paste some “scary code” from Stack Overflow to use them.

For single type param packs use std::initializer_list for easy to handle solutions

I am sure you have seen many unfriendly-looking articles and forum discussions about programming parameter packs. And perhaps you have decided to “deal with it later”.

There is no need for that. Standard C++  makes for easy programming of parameter packs. I have prepared two functions to show you how easy.

First here is the code. It is pretty well documented; I hope. To make this code work for you with no hassle, please install and use Visual Studio 2022 community edition.

That is just “core of the trick”. We can extend it in various useful directions. We can pass the handler as the first argument for example. And if that handler is a reference to a class intance, in there we can keep the init list copied to std::vector<T>, or whatever else.

There are ways to do the above with the init lists, but I can’t think of simpler handling than using std::initializer_list made from param pack.

For single call param packs of various types, use C++ std::tuple

Please note the above code will compile only if all the arguments are of the same type. For some use cases that is a severe limitation. Thus in that scenario, we provide a solution using std::tuple. (https://en.cppreference.com/w/cpp/utility/tuple.html)

Being a “C++ beginner”, you are not actually such a beginner. And as such, you do realize we have a standard console application above and you would know which headers to include and where to put those includes.

Please compile and follow, several times, the above code through a very good Visual Studio debugger; you have installed it as instructed. That is an indispensable tool when studying C++.

The whole program in godbolt: https://godbolt.org/z/7W6doYncj

Stricly optional recap

You perhaps know we are using here  generic lambdas with a variable number of arguments.

This is just a “proof of concept” code. It is here to show you how am I using standard C++ mechanisms and coding idioms to deal with “param packs” in an easy and standard fashion.

First, we deal with “param packs” where each of them is of the same type:.

handle_single_type_param_pack(1, 2, 3);

Peeking into that function we immediately spot the “wonder” line:

auto arg_list = { args ... };

In standard C++ (17 and beyond), this declaration automatically transforms param pack expansion { args ... } into the constructor call to the std::initializer_list<T>. C++ compiler effectively replaces that line with the proper declaration of the init list:

std::initializer_list<int> arg_list{ 1,2,3 } ;

Init list has begin() and end() methods that can be used to reach parameter pack elements. No scary template programming. The compiler uses them in a C++ for range loop:

 

There are also a few type declarations in there you might find useful. Good, nice, simple …. But. What do we do if we need to pass a variable number of arguments but of a different type?

We will use C++ tuple.

Another function is dealing with the “param pack” of a multitude of types. As you can see the key line is now this one:

Again we simply expand the param pack, but this time we use it to provide a variable number of arguments to pass to the std::make_tuple function.

In here you do whatever you need to do. You have all the arguments nicely handled as elements of the std::tuple.

Conclusion

The point is here to show how standard C++ can be really simple, but functional and powerful. Not just scary-looking code.

Standard C++ has many powerful tools to solve a development task. The trick is to know them all and select the best one. Which is almost always never the most complex one.