A Few Approaches to Pass Uncertain Number of Variables in C++

Sometimes, we are supposed to accept uncertain number of arguments in C++.

In this post, I am going to introduce 3 ways to implement it, so that you can pick up one of them which is most fit for you in current situation.

1. va_list

va_list is coming from C lang. Had you ever considered how to implement the function “printf”? Of course, they are using va_list.

Here is a tiny example

/* output:
:23 args_copy[0]=3
:23 args_copy[1]=5
sum of: 1, 2, 3 = 9
*/
#include
#include

int sum(int count, ...) {
int sum = 0;
va_list args;
va_start(args, count);
va_list args_copy;

for (int j = 0; j < count; j++) {
sum += va_arg(args, int);
if (j == 0) {
// args_copy will copy args to args_copy from current
va_copy(args_copy, args);
}
}
va_end(args);

for (int j = 0; j < (count - 1); j++) {
printf("%s:%d args_copy[%d]=%d\n", __FILE__, __LINE__, j,
va_arg(args_copy, int));
}
va_end(args_copy);
return sum;
}

int main() {
printf("sum of: 1, 2, 3 = %d\n", sum(3, 1, 3, 5));
}

Please refer to this link and try yourself. You can also refer to this link for its documentation.

You will use va_start, va_copy, va_arg, va_end to initialize, copy, scan and finalize the args easily.

The positive is you can use it either in C or C++, but the negative is you can’t get the count and type of args.

2. initializer_list

initializer_list is a lightweight solution to pass a set of args to some function.

Here is a simple example for initializer_list

/* output
got: 1
got: 2
got: 3
got: 4
total size: 4
sum of them is 10
*/
#include
#include
#include

using std::cin;
using std::cout;
using std::endl;
using std::initializer_list;
using std::string;

int sum(initializer_list il) {
int sum = 0;
for (auto i : il) {
cout << "got: " << i << endl;
}
cout << "total size: " << il.size() << endl;
for (auto it = il.begin(); it != il.end(); it++) {
sum += *it;
}
return sum;
}

int main() {
cout << "sum of them is " << sum({1, 2, 3, 4}) << endl;
}

Please refer to this link and try yourself.

The positive is you can use it easily in construction function, and gather results like a vector. The negative is it is pretty hard to pass a set of args in different type.

3. template

Template is a pretty useful feature in C++.

In this way, we can create a few functions, and compiler will automatically create related functions in compile time.

Here is a small example using template parameters:

/* output:
accept: add type int, value1
accept: add type double, value2
accept: add type string, value abc=>3
sum of them is 6
*/
#include
#include

using std::cin;
using std::cout;
using std::endl;
using std::string;

class S {
public:
S() : sum_(0) {}

template
S(const Args &... args) : sum_(0) {
Add(args...);
}

S &Add(int i) {
sum_ += static_cast(i);
cout << "accept: add type int, value" << i << endl;
return *this;
}

S &Add(double i) {
sum_ += i;
cout << "accept: add type double, value" << i << endl;
return *this;
}

S &Add(const string &s) {
sum_ += s.length();
cout << "accept: add type string, value "
<< s <" << s.length() << endl;
return *this;
}

template
S &Add(const First &first, const Rest &... rest) {
return Add(first).Add(rest...);
}

double val() { return sum_; }

private:
double sum_;
};

int main() {
S s(1, 2.0, string("abc"));
cout << "sum of them is " << s.val() << endl;
}

Please refer to this link and try yourself.

The positive is you can handle a lot of certain situation, and will quickly failed in compile time, the negative is it really spend a lot of time in coding.

4. template + initializer_list

If we wish to implement a API, which will accept a set of similar args. we can also use template as the frontend, and use initializer_list as the backend.

For example, if we need to accept a set of string args (whatever the input is string, const string, or const char *), the implement may seems as follow:

/* output:
got: a
got: bc
got: def
the result is: abcdef
*/
#include
#include
#include

using std::cin;
using std::cout;
using std::endl;
using std::string;
using std::initializer_list;

std::string StrCatImpl(initializer_list sl) {
std::string base;
for (auto s : sl) {
cout << "got: " << s << endl;
base += s;
}
return base;
}

template
string StrCat(const T &... args) noexcept {
return StrCatImpl({args...});
}

int main() {
std::string s = StrCat("a", "bc", string("def"));
cout << "the result is: " << s << endl;
}

Please refer to this link and try yourself.