Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

Variadic Functions

Last updated Jan 1, 2003.

A function usually takes a fixed number of arguments whose types are known at compile-time. In certain applications, however, you need to pass a variable number of arguments to a function. Fortunately, C provides a special mechanism for defining functions that take a variable number of arguments. These are known as variable argument list functions or variadic functions. printf() and scanf() are classic examples of this.

Although C++ offers superior solutions to this problem (I will get to them in a moment), it's advantageous for programmers to be familiar with variadic functions. For instance, you might encounter C++ implementations for embedded systems that don't support templates or wchar_t streams. Furthermore, in pure C environments, variadic functions have no alternative.

Uses of Variadic Functions

A variadic function must take at least one named argument. Usually, that's a format string. A special mechanism detects the number of the additional unnamed arguments and their types. Here's a typical variadic function declaration:

void printf(const char * fmt,...);

The ellipsis indicates that the function may take any number of arguments after the mandatory argument fmt.

Suppose you want to write a function, for debugging purposes, that accepts a format string indicating the type of each argument and a variable list of arguments. The function will display each argument on the screen. The function's prototype looks like this:

void emit(const char *fmt,...);

For the sake of brevity, the format codes shall be 'd' for double and 'i' for int. However, you can easily extend the functionality as needed and add support for additional built-in and user-defined types.

Implementation

The standard header <cstdarg> (<stdarg.h> in C) defines the necessary macros for traversing a variadic function's unnamed arguments. A variable of type va_list traditionally named ap ("argument pointer") will refer to each argument in turn. The va_start() macro initializes ap to point to the first unnamed argument. va_start() takes ap and the last named argument of the variadic function. For example:

//make ap point to the first arg after fmt
va_start(ap, fmt); 

The third macro, va_arg() takes ap and the current argument's type-name. It returns that argument by value and advances ap to the next argument (emit() relies on the format string to detect the end of the argument list). Finally, the macro va_end(ap) is called before the function returns to perform necessary cleanup operations. Here's the complete emit() function:

void emit(const char *fmt,...)
{
 va_list ap /*will point to each unnamed argument in turn*/
 int num;
 double d;
 const char *p=fmt;
 va_start(ap,fmt); /* point to first element after fmt*/
 while(*p)
 {
  if (*p=='i') /*int*/
  {
  num=va_arg(ap,int);
  printf("%d",num);
  }
  else if (*p=='d') /*double*/
  {
  d=va_arg(ap,double);
  printf("%f",d);
  }
  else 
  {
  printf("unsupported format flag");
  break;
  }
  ++p; /* get the next char of the format string */
 }/*while*/
 va_end(ap) /*cleanup*/ 
}

You use emit() like this:

int main()
{
 double salary;
 int id;
 // ... obtain id and calculate salary
 emit("di",salary, id); 
 int age, double bonus;
 //...
 emit("diid",salary, id, age, bonus);
}

Object Oriented Alternatives to Variadic Functions

As you may have noticed, the implementation of variadic functions isn't trivial. Failing to initialize ap, calling va_arg() one time too many or omitting the va_end(ap) call can crash your program. Furthermore, even if your coding practices are thorough, there is always a risk of passing an incorrect format string accidentally, such as:

emit("i",salary); // i used instead of d

If you're using a pure C compiler, this is the best you can get as far as variadic functions are concerned. C++, on the other hand, offers several alternatives to this technique that you should consider first.

Default Arguments

Declaring a function with default parameter values enables you to control the number of actual arguments being passed to it. For example:

void emit(int n, const char * str=0);

To display only one argument of type int, you can omit the second argument. emit() in turn will ignore a NULL pointer and display only the first argument. However, unlike with a variadic function, this solution doesn't allow you to change arguments' types.

Function Templates

A second solution is to use a function template:

template <class T> void emit(const T& arg);

In this case, you can control the type of the argument(s) but not their number.

Packing Arguments in a Container

You can pack the arguments in an STL container and pass it to a function template:

template < class T > void emit(const vector< T >& args);

This method allows you to pass any number of arguments of any type. However, on each function invocation, all the arguments must be of the same type.

Overloading operator <<

As neat as the previous three alternatives are, none offers the flexibility of variadic functions. Which brings us to the fourth alternative, namely overloading the << operator.

The standard objects cout, clog and cerr overload the << operator so that you can chain any number of arguments within a single output statement. For example:

cout << num << firstname << salary;

In this case, both the number of arguments and their types are flexible. In addition, you may define additional overloaded versions of << to support user-defined types. Most importantly -- this technique is type-safe.

Summary

Variadic functions are considered by many a thing of the past. In most cases, C++ offers superior alternatives that are both safer and simpler. That said, in certain cases you might still need to use variadic functions or even write them. In these cases, make sure that your program follows all the rules listed above; it is easy to mess things up thereby incurring unpredictable results at runtime.