va_args replacement in C++

Xianbo QIAN
2 min readJan 30, 2020

--

va_args is a commonly used trick to support arbitrary number of argument, however it has a few problems. For example, it implicitly does type promotion, e.g. promoting float to double, which is undesirable for 32bit embedded systems as double is software simulated so very slow.

Is there a proper C++ alternative? Yes. This stackoverflow answer suggests to use template expansion.

Here is a more concrete example. We want to make a C++ friendly Print function, which takes arbitrary number of arguments. We omit the style part for simplicity. The entire code can be found from godbolt.

First, let’s define a few primitive methods

void Puts(int a) {printf("%d", a);}void Puts(float a) {printf("%f", a);}void Puts(const char* s) {printf("%s", s);}

Our Print method will be based on this:

// To end the recursionvoid Print() {}template<typename FirstType, typename ...Args>void Print(FirstType&& first, Args&& ...args){
// Handle first type
Puts(std::forward<FirstType&&>(first));
// Handle the rest
Print(std::forward<Args>(args)...);}}

Now we can call it in the main function

int main(int argv, char* argc[]){Print(12, "hi", 12.f);return 0;
}

Very intuitive. Isn’t it?

Alternatively, the stackoverflow answer also suggested an iterative approach.

#define FOREACH_VARIADIC(EXPR) (int[]){((void)(EXPR),0)...,0}template<typename...Args>void PrintIterative(Args&&...args) {FOREACH_VARIADIC(Puts(std::forward<Args>(args)));}

This solution looks strange from the first sight, but it’s actually quite intuitive. Please note that parameter packs can only be expanded in certain context, such as array definition.

FOREACH_VARIADIC is basically defining an integer list, full of zeros. e.g.

int a[2] = {(1,2), (2,3)}

is basically

int a[2] = {2, 3}

But the side effect is not short-circuited, so FOREACH_VARIADIC will also trigger EXPR…

Now a quick quiz for our kind reader.

Suppose that we have 3 structs:

struct A {int value;};struct B {float score;};struct C {char* arr;};

with their value functions:

int Value(struct A a) { return a.value; }float Value(struct B b) { return b.score; }char Value(struct C c) { return *c.arr; }

How to create a sum function that will do the correct thing for the following code?

struct A a = {argv};struct B b = {3.f};struct C c = {argc[0]};Print(Sum(a, c));

The answer is here.

int Sum_(std::initializer_list<float> v) {int out = 0;std::for_each(v.begin(), v.end(), [&out](int v) { out += v; });return out;}template <typename... Args>int Sum(Args&&... args) {return Sum_(std::initializer_list<float>{(float)(Value(std::forward<Args>(args)))...});}

Remember: everything is known at compile time. With modern compiler optimization, the generated code is and should be very efficient.

--

--

No responses yet