Sometimes we do not know in advance how many arguments we need to pass to a function. For example, we might want to write a routine to print error messages generated from our program. We’d like to use a single function to print these error messages in order to handle them in a uniform way. However, different calls to our error-printing function might pass different arguments, corresponding to different kinds of error messages.
The new standard provides two primary ways to write a function that takes a varying number of arguments: If all the arguments have the same type, we can pass a library type named initializer_list
. If the argument types vary, we can write a special kind of function, known as a variadic template, which we’ll cover in § 16.4 (p. 699).
C++ also has a special parameter type, ellipsis, that can be used to pass a varying number of arguments. We’ll look briefly at ellipsis parameters in this section. However, it is worth noting that this facility ordinarily should be used only in programs that need to interface to C functions.
initializer_list
ParametersWe can write a function that takes an unknown number of arguments of a single type by using an initializer_list
parameter. An initializer_list
is a library type that represents an array (§ 3.5, p. 113) of values of the specified type. This type is defined in the initializer_list
header. The operations that initializer_list
provides are listed in Table 6.1.
Like a vector
, initializer_list
is a template type (§ 3.3, p. 96). When we define an initializer_list
, we must specify the type of the elements that the list will contain:
initializer_list<string> ls; // initializer_list of strings
initializer_list<int> li; // initializer_list of ints
Unlike vector
, the elements in an initializer_list
are always const
values; there is no way to change the value of an element in an initializer_list
.
We can write our function to produce error messages from a varying number of arguments as follows:
void error_msg(initializer_list<string> il)
{
for (auto beg = il.begin(); beg != il.end(); ++beg)
cout << *beg << " " ;
cout << endl;
}
The begin
and end
operations on initializer_list
objects are analogous to the corresponding vector
members (§ 3.4.1, p. 106). The begin()
member gives us a pointer to the first element in the list, and end()
is an off-the-end pointer one past the last element. Our function initializes beg
to denote the first element and iterates through each element in the initializer_list
. In the body of the loop we dereference beg
in order to access the current element and print its value.
When we pass a sequence of values to an initializer_list
parameter, we must enclose the sequence in curly braces:
// expected, actual are strings
if (expected != actual)
error_msg({"functionX", expected, actual});
else
error_msg({"functionX", "okay"});
Here we’re calling the same function, error_msg
, passing three values in the first call and two values in the second.
A function with an initializer_list
parameter can have other parameters as well. For example, our debugging system might have a class, named ErrCode
, that represents various kinds of errors. We can revise our program to take an ErrCode
in addition to an initializer_list
as follows:
void error_msg(ErrCode e, initializer_list<string> il)
{
cout << e.msg() << ": ";
for (const auto &elem : il)
cout << elem << " " ;
cout << endl;
}
Because initializer_list
has begin
and end
members, we can use a range for
(§ 5.4.3, p. 187) to process the elements. This program, like our previous version, iterates an element at a time through the braced list of values passed to the il
parameter.
To call this version, we need to revise our calls to pass an ErrCode
argument:
if (expected != actual)
error_msg(ErrCode(42), {"functionX", expected, actual});
else
error_msg(ErrCode(0), {"functionX", "okay"});
Ellipsis parameters are in C++ to allow programs to interface to C code that uses a C library facility named varargs
. Generally an ellipsis parameter should not be used for other purposes. Your C compiler documentation will describe how to use varargs
.
Ellipsis parameters should be used only for types that are common to both C and C++. In particular, objects of most class types are not copied properly when passed to an ellipsis parameter.
An ellipsis parameter may appear only as the last element in a parameter list and may take either of two forms:
void foo(parm_list, ...);
void foo(...);
The first form specifies the type(s) for some of foo
’s parameters. Arguments that correspond to the specified parameters are type checked as usual. No type checking is done for the arguments that correspond to the ellipsis parameter. In this first form, the comma following the parameter declarations is optional.
Exercise 6.27: Write a function that takes an initializer_list<int>
and produces the sum of the elements in the list.
Exercise 6.28: In the second version of error_msg
that has an ErrCode
parameter, what is the type of elem
in the for
loop?
Exercise 6.29: When you use an initializer_list
in a range for
would you ever use a reference as the loop control variable? If so, why? If not, why not?