Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Top-level const doesn't influence a function signature

From the C++ Primer 5th Edition, it says:

int f(int){ /* can write to parameter */}
int f(const int){ /* cannot write to parameter */}

The two functions are indistinguishable. But as you know, the two functions really differ in how they can update their parameters.

Can someone explains to me?


EDIT
I think I didn't interpret my question well. What I really care is why C++ doesn't allow these two functions simultaneously as different function since they are really different as to "whether parameter can be written or not". Intuitively, it should be!


EDIT
The nature of pass by value is actually pass by copying argument values to parameter values. Even for references and pointers where thee copied values are addresses. From the caller's viewpoint, whether const or non-const is passed to the function does not influence values (and of course types of) copied to parameters.
The distinction between top-level const and low-level const matters when copying objects. More specifically, top-level const(not the case of low-level const) is ignored when copying objects since copying won't influence the copied object. It is immaterial whether the object copied to or copied from is const or not.
So for the caller, differentiating them is not necessary. Likely, from the function viewpoint, the top-level const parameters doesn't influence the interface and/or the functionality of function. The two function actually accomplish the same thing. Why bother implementing two copies?

like image 980
Zachary Avatar asked Jun 20 '13 08:06

Zachary


People also ask

Is const part of function signature?

A function declaration tells the compiler the function's signature and return type. In the above example, the function's signature is F(int) . The constness of the function's parameter type is ignored, so both declarations are equivalent (See “Overloadable declarations”.)

What is top level const?

Top-level const: indicate that the pointer itself is a const. Top-level const can appear in any object type, i.e., one of the built-in arithmetic types, a class type, or a pointer type. Low-level const: a pointer that points to a const object.

What effect does the const qualifier have when applied to a function argument?

Declaring the parameter 'const' adds semantic information to the parameter. They highlight what the original author of the code had intended and this will aid maintenance of the code as time goes by.

What effect does the const keyword have on a function parameter?

Declaring function parameters const indicates that the function promises not to change these values. In C, function arguments are passed by value rather than by reference. Although a function may change the values passed in, these changed values are discarded once the function returns.


1 Answers

allow these two functions simultaneously as different function since they are really different as to "whether parameter can be written or not". Intuitively, it should be!

Overloading of functions is based on the parameters the caller provides. Here, it's true that the caller may provide a const or non-const value but logically it should make no difference to the functionality that the called function provides. Consider:

f(3);
int x = 1 + 2;
f(x);

If f() does different thing in each of these situations, it would be very confusing! The programmer of this code calling f() can have a reasonable expectation of identical behaviour, freely adding or removing variables that pass parameters without it invalidating the program. This safe, sane behaviour is the point of departure that you'd want to justify exceptions to, and indeed there is one - behaviours can be varied when the function's overloaded ala:

void f(const int&) { ... }
void f(int&) { ... }

So, I guess this is what you find non-intuitive: that C++ provides more "safety" (enforced consistent behaviour through supporting only a single implementation) for non-references than references.

The reasons I can think of are:

  • So when a programmer knows a non-const& parameter will have a longer lifetime, they can select an optimal implementation. For example, in the code below it may be faster to return a reference to a T member within F, but if F is a temporary (which it might be if the compiler matches const F&) then a by-value return is needed. This is still pretty dangerous as the caller has to be aware that the returned reference is only valid as long as the parameter's around.
    T f(const F&);
    T& f(F&);    // return type could be by const& if more appropriate
  • propagation of qualifiers like const-ness through function calls as in:
    const T& f(const F&);
    T& f(F&);

Here, some (presumably F member-) variable of type T is being exposed as const or non-const based on the const-ness of the parameter when f() is called. This type of interface might be chosen when wishing to extend a class with non-member functions (to keep the class minimalist, or when writing templates/algos usable on many classes), but the idea is similar to const member functions like vector::operator[](), where you want v[0] = 3 allowed on a non-const vector but not a const one.

When values are accepted by value they go out of scope as the function returns, so there's no valid scenario involving returning a reference to part of the parameter and wanting to propagate its qualifiers.

Hacking the behaviour you want

Given the rules for references, you can use them to get the kind of behaviour you want - you just need to be careful not to modify the by-non-const-reference parameter accidentally, so might want to adopt a practice like the following for the non-const parameters:

T f(F& x_ref)
{
    F x = x_ref;  // or const F is you won't modify it
    ...use x for safety...
}

Recompilation implications

Quite apart from the question of why the language forbids overloading based on the const-ness of a by-value parameter, there's the question of why it doesn't insist on consistency of const-ness in the declaration and definition.

For f(const int) / f(int)... if you are declaring a function in a header file, then it's best NOT to include the const qualifier even if the later definition in an implementation file will have it. This is because during maintenance the programmer may wish to remove the qualifier... removing it from the header may trigger a pointless recompilation of client code, so it's better not to insist they be kept in sync - and indeed that's why the compiler doesn't produce an error if they differ. If you just add or remove const in the function definition, then it's close to the implementation where the reader of the code might care about the constness when analysing the function behaviour. If you have it const in both header and implementation file, then the programmer wishes to make it non-const and forgets or decides not to update the header in order to avoid client recompilation, then it's more dangerous than the other way around as it's possible the programmer will have the const version from the header in mind when trying to analyse the current implementation code leading to wrong reasoning about the function behaviour. This is all a very subtle maintainence issue - only really relevant to commercial programming - but that's the basis of the guideline not to use const in the interface. Further, it's more concise to omit it from the interface, which is nicer for client programmers reading over your API.

like image 50
Tony Delroy Avatar answered Oct 12 '22 13:10

Tony Delroy