6.2. Argument Passing
FundamentalAs 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
FundamentalWhen we initialize a nonreference type variable, the value of the initializer is copied. Changes made to the variable have no effect on the initializer:
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 fact
(§ 6.1, p. 202) the parameter val
is decremented:
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):
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:
// 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:
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 int
s. Test the function by calling it and printing the swapped values.
6.2.2. Passing Arguments by Reference
FundamentalRecall that operations on a reference are actually operations on the object to which the reference refers (§ 2.3.1, p. 50):
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:
// 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:
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 string
s. Because string
s can be long, we’d like to avoid copying them, so we’ll make our parameters references. Because comparing two string
s does not involve changing the string
s, we’ll make the parameters references to const
(§ 2.4.1, p. 61):
// 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:
// 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_type
(§ 3.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:
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 int
s. 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
FundamentalWhen 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:
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 const
s 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
:
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 const
s are ignored on a parameter has one possibly surprising implication:
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 const
s 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.
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:
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 reset
(§ 6.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 reset
(§ 6.2.1, p. 209). On the other hand, we can pass a string literal as the first argument to find_char
(§ 6.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
TrickyIt 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&
:
// 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
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:
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 string
s.
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:
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.
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
?
// 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*
:
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:
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 int
s, 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:
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:
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:
// 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:
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 const
(§ 2.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:
// 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):
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 int
s:
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:
// 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 int
s.
INFO
Again, the parentheses around *matrix
are necessary:
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:
// 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 int
s.
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:
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:
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.
void print(const int ia[10])
{
for (size_t i = 0; i != 10; ++i)
cout << ia[i] << endl;
}
prog -d -o ofile data0
Such command-line options are passed to main
in two (optional) parameters:
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
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:
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++11We 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_list
s
Code | Description |
---|---|
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 = lst | Copying 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:
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
AdvancedEllipsis 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:
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?