14

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?

Arch D. Robison
  • 3,829
  • 2
  • 16
  • 26
Zachary
  • 1,633
  • 2
  • 22
  • 34
  • 4
    They're indistinguishable to the caller. It shouldn't have to care whether they modify their own copies. – chris Jun 20 '13 at 08:16
  • If you were talking about references or pointers, it'd be a different story – Cogwheel Jun 20 '13 at 08:32
  • 2
    If you don't add "`&`", then the `const` is useless, since the arguments are copied. In another case, `int f(int &)` vs `int f(const int &)` would be distinguishable, and useful. – WiSaGaN Jun 20 '13 at 08:41
  • @WiSaGaN Thx. Another similar case is use pointer *. – Zachary Jun 20 '13 at 08:42
  • In that case see http://stackoverflow.com/questions/3682049/functions-with-const-arguments-and-overloading – doctorlove Jun 20 '13 at 08:42
  • However, your local style rules might mandate that's it's not good practice to change the value of function parameters within the function, hence the "const int". – MikeW Apr 27 '17 at 13:50

7 Answers7

12

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.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • **it could be implicitly worked out by the value on the basis of the value being a temporary which will be consider const** Could you give more details? – Zachary Jun 20 '13 at 08:54
  • @Zack: say you have `f(9 + 4)`, or `f(g())` given `int g();`... the compiler considers the parameter to be a temporary so will not use a function `f(int&)`, but it will be allowed to bind a non-`const` reference to the temporary so `f(const int&)` can accept the calls above. – Tony Delroy Jun 20 '13 at 08:57
  • In C++. Only const reference can be bound to temporary object. Thx. – Zachary Jun 20 '13 at 09:00
  • _removing it from the header may trigger a pointless recompilation of client code_. Why **NO const** in header file does not need recompilation of client code? – Zachary Jun 20 '13 at 09:08
  • 1
    @Zack: the point is that any change to the header triggers a recompilation, so if you want to be able to change the implementation freely sans recompilation you have to accept that the header will be out of sync with the definition sometimes. If you accept that, it's better for the header to say something more permissive than less, as there's less room for the programmer to wrongly assume it's safer/const and reason wrongly about the code behaviour. It's also more concise in the header which is nice for the client coders - they don't care either way so why tell them if it's `const`? – Tony Delroy Jun 20 '13 at 09:13
  • How can you add/remove const in implementation without recompile the client? I cannot imagine a scenario. Could you help? – Zachary Jun 20 '13 at 10:08
  • 1
    @Zack you would only have to re-link the client, unless the implementation is in a header file used by it. – juanchopanza Jun 20 '13 at 11:14
  • @juanchopanza I got it. Especially you want to implement a library. It is called separation compilation. – Zachary Jun 20 '13 at 11:38
6

Since there is no difference to the caller, and no clear way to distinguish between a call to a function with a top level const parameter and one without, the language rules ignore top level consts. This means that these two

void foo(const int);
void foo(int);

are treated as the same declaration. If you were to provide two implementations, you would get a multiple definition error.

There is a difference in a function definition with top level const. In one, you can modify your copy of the parameter. In the other, you can't. You can see it as an implementation detail. To the caller, there is no difference.

// declarations
void foo(int);
void bar(int);

// definitions
void foo(int n)
{
  n++;
  std::cout << n << std::endl;
}

void bar(const int n)
{
  n++; // ERROR!
  std::cout << n << std::endl;
}

This is analogous to the following:

void foo()
{
  int = 42;
  n++;
  std::cout << n << std::endl;
}

void bar()
{
  const int n = 42;
  n++; // ERROR!
  std::cout << n << std::endl;
}
juanchopanza
  • 223,364
  • 34
  • 402
  • 480
  • I know your point. But why C++ doesn't allow the two function have the same function name(from the caller's aspect). Intuitively, it should be! – Zachary Jun 20 '13 at 08:32
  • @Zack It is just a language rule. It is deemed that top level const makes no difference to the caller, so it is ignored in the declaration. So you should never put top level consts in your function declarations, to avoid confusion. – juanchopanza Jun 20 '13 at 08:35
  • Either function can be called as `foo(5)`. How would you resolve it if both were allowed to exist? – n. m. could be an AI Jun 20 '13 at 08:38
  • Obviously Zack's expectation was that `foo(5)` would call `foo(const int)`, and `foo(my_var)` would call `foo(int)`. It's not beyond imagination that the compiler could orchestrate that, the interesting question is why the language doesn't support it. – Tony Delroy Jun 20 '13 at 09:09
  • @TonyD My guess is that at some point it was decided that it was not worth inventing a whole set of rules just to be able to treat top level consts, given that there is no intuitive behaviour (I would not expect `foo(5)` to prefer `foo(const int)`, I would have to read up on the rules to figure out why it chose that one). – juanchopanza Jun 20 '13 at 09:44
  • @juanchopanza: respectfully, I think us old C++ hackers' intuition on this is distorted by having known it's illegal for years. One trivial-effort possibility: choose between `foo(const int)` or `foo(int)` just as you'd choose between `foo(const int&)` and `foo(int&)`. Anyway, I think it's forbidden as it's dangerous and not useful (as per my answer), rather than that it's difficult to implement or have intuitive. – Tony Delroy Jun 20 '13 at 10:38
  • @TonyD Of course, I was only speculating, and you are right that after many years the existing rules start to become intuitive. I will have a look at "the design and evolution..." later. I will up-vote your answer too! – juanchopanza Jun 20 '13 at 11:14
  • @TonyD This is (a) a radical change from C that breaks both C compatibility and about 100% of existing code an (b) serves no useful purpose. Looks like a no-brainer. – n. m. could be an AI Jun 20 '13 at 13:24
  • @n.m. (a) not true... could have the routing rule only kick in when the function's overloaded - which is currently illegal - so no affect to existing code (b) asserting that's less useful than what this question calls for - explaining it, particularly in comparison to the legal overloading for const and non-const references ;-) – Tony Delroy Jun 20 '13 at 22:22
6

In "The C++ Programming Language", fourth edition, Bjarne Stroustrup writes (§12.1.3):

Unfortunately, to preserve C compatibility, a const is ignored at the highest level of an argument type. For example, this is two declarations of the same function:

void f(int);
void f(const int);

So, it seems that, contrarily to some of the other answers, this rule of C++ was not chosen because of the indistinguishability of the two functions, or other similar rationales, but instead as a less-than-optimal solution, for the sake of compatibility.

Indeed, in the D programming language, it is possible to have those two overloads. Yet, contrarily to what other answers to this question might suggest, the non-const overload is preferred if the function is called with a literal:

void f(int);
void f(const int);

f(42); // calls void f(int);

Of course, you should provide equivalent semantics for your overloads, but that is not specific to this overloading scenario, with nearly indistinguishable overloading functions.

Luís Marques
  • 991
  • 1
  • 10
  • 20
1

As the comments say, inside the first function the parameter could be changed, if it had been named. It is a copy of the callee's int. Inside the second function, any changes to the parameter, which is still a copy of the callee's int, will result in a compile error. Const is a promise you won't change the variable.

doctorlove
  • 18,872
  • 2
  • 46
  • 62
1

A function is useful only from the caller's perspective.

Since there is no difference to the caller, there is no difference, for these two functions.

0

I think the indistinguishable is used in the terms of overloading and compiler, not in terms if they can be distinguished by caller.

Compiler does not distinguish between those two functions, their names are mangled in the same way. That leads to situation when compiler treats those two declarations as redefinition.

undercover
  • 176
  • 4
0

Answering this part of your question:

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!

If you think about it a little more, it isn't at all intinuitive - in fact, it doesn't make much sense. As everybody else has said, a caller is in no way influenced when a functon takes it's parameter by value and it doesn't care, either.

Now, let's suppose for a moment that overload resolution worked on top level const, too. Two declarations like this

int foo(const int);
int foo(int);

would declare two different functions. One of the problems would be which functions would this expression call: foo(42). The language rules could say that literals are const and that the const "overload" would be called in this case. But that's the least of a problem. A programmer feeling sufficiently evil could write this:

int foo(const int i) { return i*i; }
int foo(int i)       { return i*2; }

Now you'd have two overloads that are appear semanticaly equivalent to the caller but do completely different things. Now that would be bad. We'd be able to write interfaces that limit the user by the way they do things, not by what they offer.

jrok
  • 54,456
  • 9
  • 109
  • 141
  • It is really not intuitive at all since the top-level const function called by value. So from the caller's perspective, it doesn't care. – Zachary Jun 20 '13 at 08:57
  • That's right, the caller doesn't care. Then why be limited by an implementation detail that doesn't directly influence the client code? – jrok Jun 20 '13 at 08:59
  • Please refer to @Tony D's answer. The client code only needs to include the header file. Usually, in the header file, _NO_ **const** is used in function declaration. – Zachary Jun 20 '13 at 09:04
  • @Zack TonyD explains quite well why is that so. But if language worked they you think it should, then you'd **have** to use const in headers, as if it would be a part of signature. – jrok Jun 20 '13 at 09:13
  • **const** is just a qualifier. It does not comprise the function signature. – Zachary Jun 20 '13 at 09:49
  • 1
    @Zack Erm ok, I know that. Parameter types do (and `const` qualifier does make a difference when it's not at top level, e.g. `const int&` vs `int&`). Thing is, if you wanted the language to treat `int foo(const int)` and `int foo(int)` as declarations of two different functions, the top level `const` **would be** (indirectly, if you want) a part of the signature. I'm not sure anymore where you confusion comes from and what do you want to hear from answerers. – jrok Jun 20 '13 at 09:56
  • I have got your ideas. I only care top-level type qualifiers. For the low-level ones, it is a different story. – Zachary Jun 20 '13 at 10:04
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/32069/discussion-between-zack-and-jrok) – Zachary Jun 20 '13 at 10:08