Skip to content

6.2. Argument Passing

Fundamental

As we’ve seen, each time we call a function, its parameters are created and initialized by the arguments passed in the call.

INFO

Parameter initialization works the same way as variable initialization.

As with any other variable, the type of a parameter determines the interaction between the parameter and its argument. If the parameter is a reference (§ 2.3.1, p. 50), then the parameter is bound to its argument. Otherwise, the argument’s value is copied.

When a parameter is a reference, we say that its corresponding argument is “passed by reference” or that the function is “called by reference.” As with any other reference, a reference parameter is an alias for the object to which it is bound; that is, the parameter is an alias for its corresponding argument.

When the argument value is copied, the parameter and argument are independent objects. We say such arguments are “passed by value” or alternatively that the function is “called by value.”

6.2.1. Passing Arguments by Value

Fundamental

When we initialize a nonreference type variable, the value of the initializer is copied. Changes made to the variable have no effect on the initializer:

c++
int n = 0;             // ordinary variable of type int
int i = n;             // i is a copy of the value in n
i = 42;                // value in i is changed; n is unchanged

Passing an argument by value works exactly the same way; nothing the function does to the parameter can affect the argument. For example, inside fact6.1, p. 202) the parameter val is decremented:

c++
ret *= val--;  // decrements the value of val

Although fact changes the value of val, that change has no effect on the argument passed to fact. Calling fact(i) does not change the value of i.

Pointer Parameters

Pointers (§ 2.3.2, p. 52) behave like any other nonreference type. When we copy a pointer, the value of the pointer is copied. After the copy, the two pointers are distinct. However, a pointer also gives us indirect access to the object to which that pointer points. We can change the value of that object by assigning through the pointer (§ 2.3.2, p. 55):

c++
int n = 0, i = 42;
int *p = &n, *q = &i; // p points to n; q points to i
*p = 42;              // value in n is changed; p is unchanged
p = q;                // p now points to i; values in i and n are unchanged

The same behavior applies to pointer parameters:

c++
// function that takes a pointer and sets the pointed-to value to zero
void reset(int *ip)
{
    *ip = 0;  // changes the value of the object to which ip points
    ip = 0;   // changes only the local copy of ip; the argument is unchanged
}

After a call to reset, the object to which the argument points will be 0, but the pointer argument itself is unchanged:

c++
int i = 42;
reset(&i);                    // changes i but not the address of i
cout << "i = " << i << endl;  // prints i = 0

TIP

Best Practices

Programmers accustomed to programming in C often use pointer parameters to access objects outside a function. In C++, programmers generally use reference parameters instead.

INFO

Exercises Section 6.2.1

Exercise 6.10: Using pointers, write a function to swap the values of two ints. Test the function by calling it and printing the swapped values.

6.2.2. Passing Arguments by Reference

Fundamental

Recall that operations on a reference are actually operations on the object to which the reference refers (§ 2.3.1, p. 50):

c++
int n = 0, i = 42;
int &r = n;           // r is bound to n (i.e., r is another name for n)
r = 42;               // n is now 42
r = i;                // n now has the same value as i
i = r;                // i has the same value as n

Reference parameters exploit this behavior. They are often used to allow a function to change the value of one or more of its arguments.

As one example, we can rewrite our reset program from the previous section to take a reference instead of a pointer:

c++
// function that takes a reference to an int and sets the given object to zero
void reset(int &i)  // i is just another name for the object passed to reset
{
    i = 0;  // changes the value of the object to which i refers
}

As with any other reference, a reference parameter is bound directly to the object from which it is initialized. When we call this version of reset, i will be bound to whatever int object we pass. As with any reference, changes made to i are made to the object to which i refers. In this case, that object is the argument to reset.

When we call this version of reset, we pass an object directly; there is no need to pass its address:

c++
int j = 42;
reset(j);  // j is passed by reference; the value in j is changed
cout << "j = " << j  << endl;  // prints j = 0

In this call, the parameter i is just another name for j. Any use of i inside reset is a use of j.

Using References to Avoid Copies

It can be inefficient to copy objects of large class types or large containers. Moreover, some class types (including the IO types) cannot be copied. Functions must use reference parameters to operate on objects of a type that cannot be copied.

As an example, we’ll write a function to compare the length of two strings. Because strings can be long, we’d like to avoid copying them, so we’ll make our parameters references. Because comparing two strings does not involve changing the strings, we’ll make the parameters references to const2.4.1, p. 61):

c++
// compare the length of two strings
bool isShorter(const string &s1, const string &s2)
{
    return s1.size() < s2.size();
}

As we’ll see in § 6.2.3 (p. 213), functions should use references to const for reference parameters they do not need to change.

TIP

Best Practices

Reference parameters that are not changed inside a function should be references to const.

Using Reference Parameters to Return Additional Information

A function can return only a single value. However, sometimes a function has more than one value to return. Reference parameters let us effectively return multiple results. As an example, we’ll define a function named find_char that will return the position of the first occurrence of a given character in a string. We’d also like the function to return a count of how many times that character occurs.

How can we define a function that returns a position and an occurrence count? We could define a new type that contains the position and the count. An easier solution is to pass an additional reference argument to hold the occurrence count:

c++
// returns the index of the first occurrence of c in s
// the reference parameter occurs counts how often c occurs
string::size_type find_char(const string &s, char c,
                           string::size_type &occurs)
{
    auto ret = s.size();   // position of the first occurrence, if any
    occurs = 0;            // set the occurrence count parameter
    for (decltype(ret) i = 0; i != s.size(); ++i) {
        if (s[i] == c) {
            if (ret == s.size())
                ret = i;   // remember the first occurrence of c
            ++occurs;      // increment the occurrence count
         }
    }
    return ret;            // count is returned implicitly in occurs
}

When we call find_char, we have to pass three arguments: a string in which to look, the character to look for, and a size_type3.2.2, p. 88) object to hold the occurrence count. Assuming s is a string, and ctr is a size_type object, we can call find_char as follows:

c++
auto index = find_char(s, 'o', ctr);

After the call, the value of ctr will be the number of times o occurs, and index will refer to the first occurrence if there is one. Otherwise, index will be equal to s.size() and ctr will be zero.

INFO

Exercises Section 6.2.2

Exercise 6.11: Write and test your own version of reset that takes a reference.

Exercise 6.12: Rewrite the program from exercise 6.10 in § 6.2.1 (p. 210) to use references instead of pointers to swap the value of two ints. Which version do you think would be easier to use and why?

Exercise 6.13: Assuming T is the name of a type, explain the difference between a function declared as void f(T) and void f(T&).

Exercise 6.14: Give an example of when a parameter should be a reference type. Give an example of when a parameter should not be a reference.

Exercise 6.15: Explain the rationale for the type of each of find_char’s parameters In particular, why is s a reference to const but occurs is a plain reference? Why are these parameters references, but the char parameter c is not? What would happen if we made s a plain reference? What if we made occurs a reference to const?

6.2.3. const Parameters and Arguments

Fundamental

When we use parameters that are const, it is important to remember the discussion of top-level const from § 2.4.3 (p. 63). As we saw in that section, a top-level const is one that applies to the object itself:

c++
const int ci = 42;     // we cannot change ci; const is top-level
int i = ci;            // ok: when we copy ci, its top-level const is ignored
int * const p = &i;    // const is top-level; we can't assign to p
*p = 0;                // ok: changes through p are allowed; i is now 0

Just as in any other initialization, when we copy an argument to initialize a parameter, top-level consts are ignored. As a result, top-level const on parameters are ignored. We can pass either a const or a nonconst object to a parameter that has a top-level const:

c++
void fcn(const int i) { /* fcn can read but not write to i */ }

We can call fcn passing it either a const int or a plain int. The fact that top-level consts are ignored on a parameter has one possibly surprising implication:

c++
void fcn(const int i) { /* fcn can read but not write to i */ }
void fcn(int i) { /* . . . */ } // error: redefines fcn(int)

In C++, we can define several different functions that have the same name. However, we can do so only if their parameter lists are sufficiently different. Because top-level consts are ignored, we can pass exactly the same types to either version of fcn. The second version of fcn is an error. Despite appearances, its parameter list doesn’t differ from the list in the first version of fcn.

Pointer or Reference Parameters and const

Because parameters are initialized in the same way that variables are initialized, it can be helpful to remember the general initialization rules. We can initialize an object with a low-level const from a nonconst object but not vice versa, and a plain reference must be initialized from an object of the same type.

c++
int i = 42;
const int *cp = &i; // ok: but cp can't change i (§ 2.4.2 (p. 62))
const int &r = i;   // ok: but r can't change i (§ 2.4.1 (p. 61))
const int &r2 = 42; // ok: (§ 2.4.1 (p. 61))
int *p = cp;  // error: types of p and cp don't match (§ 2.4.2 (p. 62))
int &r3 = r;  // error: types of r3 and r don't match (§ 2.4.1 (p. 61))
int &r4 = 42; // error: can't initialize a plain reference from a literal (§ 2.3.1 (p. 50))

Exactly the same initialization rules apply to parameter passing:

c++
int i = 0;
const int ci = i;
string::size_type ctr = 0;
reset(&i);   // calls the version of reset that has an int* parameter
reset(&ci);  // error: can't initialize an int* from a pointer to a const int object
reset(i);    // calls the version of reset that has an int& parameter
reset(ci);   // error: can't bind a plain reference to the const object ci
reset(42);   // error: can't bind a plain reference to a literal
reset(ctr);  // error: types don't match; ctr has an unsigned type
// ok: find_char's first parameter is a reference to const
find_char("Hello World!", 'o', ctr);

We can call the reference version of reset6.2.2, p. 210) only on int objects. We cannot pass a literal, an expression that evaluates to an int, an object that requires conversion, or a const int object. Similarly, we may pass only an int* to the pointer version of reset6.2.1, p. 209). On the other hand, we can pass a string literal as the first argument to find_char6.2.2, p. 211). That function’s reference parameter is a reference to const, and we can initialize references to const from literals.

Use Reference to const When Possible
Tricky

It is a somewhat common mistake to define parameters that a function does not change as (plain) references. Doing so gives the function’s caller the misleading impression that the function might change its argument’s value. Moreover, using a reference instead of a reference to const unduly limits the type of arguments that can be used with the function. As we’ve just seen, we cannot pass a const object, or a literal, or an object that requires conversion to a plain reference parameter.

The effect of this mistake can be surprisingly pervasive. As an example, consider our find_char function from § 6.2.2 (p. 211). That function (correctly) made its string parameter a reference to const. Had we defined that parameter as a plain string&:

c++
// bad design: the first parameter should be a const string&
string::size_type find_char(string &s, char c,
                           string::size_type &occurs);

we could call find_char only on a string object. A call such as

c++
find_char("Hello World", 'o', ctr);

would fail at compile time.

More subtly, we could not use this version of find_char from other functions that (correctly) define their parameters as references to const. For example, we might want to use find_char inside a function that determines whether a string represents a sentence:

c++
bool is_sentence(const string &s)
{
    // if there's a single period at the end of s, then s is a sentence
    string::size_type ctr = 0;
    return find_char(s, '.', ctr) == s.size() - 1 && ctr == 1;
}

If find_char took a plain string&, then this call to find_char would be a compile-time error. The problem is that s is a reference to a const string, but find_char was (incorrectly) defined to take a plain reference.

It might be tempting to try to fix this problem by changing the type of the parameter in is_sentence. But that fix only propagates the error—callers of is_sentence could pass only nonconst strings.

The right way to fix this problem is to fix the parameter in find_char. If it’s not possible to change find_char, then define a local string copy of s inside is_sentence and pass that string to find_char.

6.2.4. Array Parameters

Arrays have two special properties that affect how we define and use functions that operate on arrays: We cannot copy an array (§ 3.5.1, p. 114), and when we use an array it is (usually) converted to a pointer (§ 3.5.3, p. 117). Because we cannot copy an array, we cannot pass an array by value. Because arrays are converted to pointers, when we pass an array to a function, we are actually passing a pointer to the array’s first element.

Even though we cannot pass an array by value, we can write a parameter that looks like an array:

INFO

Exercises Section 6.2.3

Exercise 6.16: The following function, although legal, is less useful than it might be. Identify and correct the limitation on this function:

c++
bool is_empty(string& s) { return s.empty(); }

Exercise 6.17: Write a function to determine whether a string contains any capital letters. Write a function to change a string to all lowercase. Do the parameters you used in these functions have the same type? If so, why? If not, why not?

Exercise 6.18: Write declarations for each of the following functions. When you write these declarations, use the name of the function to indicate what the function does.

(a) A function named compare that returns a bool and has two parameters that are references to a class named matrix.

(b) A function named change_val that returns a vector<int> iterator and takes two parameters: One is an int and the other is an iterator for a vector<int>.

Exercise 6.19: Given the following declarations, determine which calls are legal and which are illegal. For those that are illegal, explain why.

c++
double calc(double);
int count(const string &, char);
int sum(vector<int>::iterator, vector<int>::iterator, int);
vector<int> vec(10);

(a)calc(23.4, 55.1);

(b)count("abcda", 'a');

(c)calc(66);

(d)sum(vec.begin(), vec.end(), 3.8);

Exercise 6.20: When should reference parameters be references to const? What happens if we make a parameter a plain reference when it could be a reference to const?

c++
// despite appearances, these three declarations of print are equivalent
// each function has a single parameter of type const int*
void print(const int*);
void print(const int[]);   // shows the intent that the function takes an array
void print(const int[10]); // dimension for documentation purposes (at best)

Regardless of appearances, these declarations are equivalent: Each declares a function with a single parameter of type const int*. When the compiler checks a call to print, it checks only that the argument has type const int*:

c++
int i = 0, j[2] = {0, 1};
print(&i); // ok: &i is int*
print(j);  // ok: j is converted to an int* that points to j[0]

If we pass an array to print, that argument is automatically converted to a pointer to the first element in the array; the size of the array is irrelevant.

WARNING

As with any code that uses arrays, functions that take array parameters must ensure that all uses of the array stay within the array bounds.

Because arrays are passed as pointers, functions ordinarily don’t know the size of the array they are given. They must rely on additional information provided by the caller. There are three common techniques used to manage pointer parameters.

Using a Marker to Specify the Extent of an Array

The first approach to managing array arguments requires the array itself to contain an end marker. C-style character strings (§ 3.5.4, p. 122) are an example of this approach. C-style strings are stored in character arrays in which the last character of the string is followed by a null character. Functions that deal with C-style strings stop processing the array when they see a null character:

c++
void print(const char *cp)
{
    if (cp)          // if cp is not a null pointer
        while (*cp)  // so long as the character it points to is not a null character
            cout << *cp++; // print the character and advance the pointer
}

This convention works well for data where there is an obvious end-marker value (like the null character) that does not appear in ordinary data. It works less well with data, such as ints, where every value in the range is a legitimate value.

Using the Standard Library Conventions

A second technique used to manage array arguments is to pass pointers to the first and one past the last element in the array. This approach is inspired by techniques used in the standard library. We’ll learn more about this style of programming in Part II. Using this approach, we’ll print the elements in an array as follows:

c++
void print(const int *beg, const int *end)
{
    // print every element starting at beg up to but not including end
    while (beg != end)
        cout << *beg++ << endl; // print the current element
                                // and advance the pointer
}

The while uses the dereference and postfix increment operators (§ 4.5, p. 148) to print the current element and advance beg one element at a time through the array. The loop stops when beg is equal to end.

To call this function, we pass two pointers—one to the first element we want to print and one just past the last element:

c++
int j[2] = {0, 1};
// j is converted to a pointer to the first element in j
// the second argument is a pointer to one past the end of j
print(begin(j), end(j)); // begin and end functions, see § 3.5.3 (p. 118)

This function is safe, as long as the caller correctly calculates the pointers. Here we let the library begin and end functions (§ 3.5.3, p. 118) provide those pointers.

Explicitly Passing a Size Parameter

A third approach for array arguments, which is common in C programs and older C++ programs, is to define a second parameter that indicates the size of the array. Using this approach, we’ll rewrite print as follows:

c++
// const int ia[] is equivalent to const int* ia
// size is passed explicitly and used to control access to elements of ia
void print(const int ia[], size_t size)
{
    for (size_t i = 0; i != size; ++i) {
        cout << ia[i] << endl;
    }
}

This version uses the size parameter to determine how many elements there are to print. When we call print, we must pass this additional parameter:

c++
int j[] = { 0, 1 };  // int array of size 2
print(j, end(j) - begin(j));

The function executes safely as long as the size passed is no greater than the actual size of the array.

Array Parameters and const

Note that all three versions of our print function defined their array parameters as pointers to const. The discussion in § 6.2.3 (p. 213) applies equally to pointers as to references. When a function does not need write access to the array elements, the array parameter should be a pointer to const2.4.2, p. 62). A parameter should be a plain pointer to a nonconst type only if the function needs to change element values.

Array Reference Parameters

Just as we can define a variable that is a reference to an array (§ 3.5.1, p. 114), we can define a parameter that is a reference to an array. As usual, the reference parameter is bound to the corresponding argument, which in this case is an array:

c++
// ok: parameter is a reference to an array; the dimension is part of the type
void print(int (&arr)[10])
{
    for (auto elem : arr)
        cout << elem << endl;
}

INFO

The parentheses around &arr are necessary (§ 3.5.1, p. 114):

c++
f(int &arr[10])   // error: declares arr as an array of references
f(int (&arr)[10]) // ok: arr is a reference to an array of ten ints

Because the size of an array is part of its type, it is safe to rely on the dimension in the body of the function. However, the fact that the size is part of the type limits the usefulness of this version of print. We may call this function only for an array of exactly ten ints:

c++
int i = 0, j[2] = {0, 1};
int k[10] = {0,1,2,3,4,5,6,7,8,9};
print(&i);   // error: argument is not an array of ten ints
print(j);    // error: argument is not an array of ten ints
print(k);    // ok: argument is an array of ten ints

We’ll see in § 16.1.1 (p. 654) how we might write this function in a way that would allow us to pass a reference parameter to an array of any size.

Passing a Multidimensional Array

Recall that there are no multidimensional arrays in C++ (§ 3.6, p. 125). Instead, what appears to be a multidimensional array is an array of arrays.

As with any array, a multidimensional array is passed as a pointer to its first element (§ 3.6, p. 128). Because we are dealing with an array of arrays, that element is an array, so the pointer is a pointer to an array. The size of the second (and any subsequent) dimension is part of the element type and must be specified:

c++
// matrix points to the first element in an array whose elements are arrays of ten ints
void print(int (*matrix)[10], int rowSize) { /* . . . */ }

declares matrix as a pointer to an array of ten ints.

INFO

Again, the parentheses around *matrix are necessary:

c++
int *matrix[10];   // array of ten pointers
int (*matrix)[10]; // pointer to an array of ten ints

We can also define our function using array syntax. As usual, the compiler ignores the first dimension, so it is best not to include it:

c++
// equivalent definition
void print(int matrix[][10], int rowSize) { /* . . . */ }

declares matrix to be what looks like a two-dimensional array. In fact, the parameter is a pointer to an array of ten ints.

6.2.5. main: Handling Command-Line Options

It turns out that main is a good example of how C++ programs pass arrays to functions. Up to now, we have defined main with an empty parameter list:

c++
int main() { ... }

However, we sometimes need to pass arguments to main. The most common use of arguments to main is to let the user specify a set of options to guide the operation of the program. For example, assuming our main program is in an executable file named prog, we might pass options to the program as follows:

INFO

Exercises Section 6.2.4

Exercise 6.21: Write a function that takes an int and a pointer to an int and returns the larger of the int value or the value to which the pointer points. What type should you use for the pointer?

Exercise 6.22: Write a function to swap two int pointers.

Exercise 6.23: Write your own versions of each of the print functions presented in this section. Call each of these functions to print i and j defined as follows:

c++
int i = 0, j[2] = {0, 1};

Exercise 6.24: Explain the behavior of the following function. If there are problems in the code, explain what they are and how you might fix them.

c++
void print(const int ia[10])
{
    for (size_t i = 0; i != 10; ++i)
        cout << ia[i] << endl;
}
shellscript
prog -d -o ofile data0

Such command-line options are passed to main in two (optional) parameters:

c++
int main(int argc, char *argv[]) { ... }

The second parameter, argv, is an array of pointers to C-style character strings. The first parameter, argc, passes the number of strings in that array. Because the second parameter is an array, we might alternatively define main as

c++
int main(int argc, char **argv) { ... }

indicating that argv points to a char*.

When arguments are passed to main, the first element in argv points either to the name of the program or to the empty string. Subsequent elements pass the arguments provided on the command line. The element just past the last pointer is guaranteed to be 0.

Given the previous command line, argc would be 5, and argv would hold the following C-style character strings:

c++
argv[0] = "prog";   // or argv[0] might point to an empty string
argv[1] = "-d";
argv[2] = "-o";
argv[3] = "ofile";
argv[4] = "data0";
argv[5] = 0;

WARNING

When you use the arguments in argv, remember that the optional arguments begin in argv[1]; argv[0] contains the program’s name, not user input.

INFO

Exercises Section 6.2.5

Exercise 6.25: Write a main function that takes two arguments. Concatenate the supplied arguments and print the resulting string.

Exercise 6.26: Write a program that accepts the options presented in this section. Print the values of the arguments passed to main.

6.2.6. Functions with Varying Parameters

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 Parameters
C++11

We 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.

Table 6.1. Operations on initializer_lists

CodeDescription
initializer_list<T> lst;Default initialization; an empty list of elements of type T.
initializer_list<T> lst {a, b, c, ...};lst has as many elements as there are initializers; elements are copies of the corresponding initializers. Elements in the list are const.
lst2(lst) lst2 = lstCopying or assigning an initializer_list does not copy the elements in the list. After the copy, the original and the copy share the elements.
lst.size()Number of elements in the list.
lst.begin() lst.end()Returns a pointer to the first and one past the last element in lst.

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:

c++
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:

c++
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:

c++
// 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:

c++
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 for5.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:

c++
if (expected != actual)
    error_msg(ErrCode(42), {"functionX", expected, actual});
else
    error_msg(ErrCode(0), {"functionX", "okay"});
Ellipsis Parameters
Advanced

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.

WARNING

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:

c++
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.

INFO

Exercises Section 6.2.6

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?