6.2.3. const Parameters and Arguments

Image

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:

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:

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:

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.

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 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
Image

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&:

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

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.212.217