views:

319

answers:

4

Take note of the following C++ code:

#include <iostream>
using std::cout;

int foo (const int);

int main ()
{
   cout << foo(3);
}

int foo (int a)
{
   a++;
   return a;
}

Notice that the prototype of foo() takes a const int and that the definition takes an int. This compile without any errors...

Why are there no compilation errors?

+25  A: 

Because it doesn't matter to the caller of the foo function whether foo modifies its copy of the variable or not.

Specifically in the C++03 standard, the following 2 snippets explain exactly why:

C++03 Section: 13.2-1

Two function declarations of the same name refer to the same function if they are in the same scope and have equivalent parameter declarations (13.1).

C++03 Section: 13.1-3

Parameter declarations that differ only in the presence or absence of const and/or volatile are equivalent. Only the const and volatile type-specifiers at the outermost level of the parameter type specification are ignored in this fashion; const and volatile type-specifiers buried within a parameter type specification are significant and can be used to distinguish overloaded function declarations.

Brian R. Bondy
+1 for quoting the spec.
casablanca
+1: The second quote is easily misunderstood in the absence of what follows in the Standard "Only the const and volatile type-specifiers at the outermost level of the parameter type specification are ignored in this fashion; const and volatile type-specifiers buried within a parameter type specification are significant and can be used to distinguish overloaded function declarations.112)". So 'int const *p' and 'int volatile *p' are different
Chubsdad
@Chubsdad: Agree I should have put more there, added it. Thanks.
Brian R. Bondy
@Brian: +1, Good answer badge coming. :)
Prasoon Saurav
A: 
int foo (const int a)
{
   a++;
   return a;
}

That'll throw an error during compiling.

HarzIce
It will, but what has that got to do with the question being asked?
DominicMcDonnell
+12  A: 

Top-level const (i.e., that applies to the value that's passed, not something to which it points or refers) affects only the implementation, not the interface, of a function. The compiler ignores it from the interface viewpoint (i.e., the calling side) and enforces it only on the implementation (i.e., code in the body of the function).

Jerry Coffin
+1 for *not* quoting the specs and instead explaining to an obviously novice user what's the rationale behind this behavior.
Matteo Italia
I quite like to see the standard quoted, so long as the quote makes sense stripped out of its context - it is, after all, the definitive point.
Steve314
@Steve314: How *dare* you imply that my saying something is any less definitive than quoting the standard! :-)
Jerry Coffin
@Steve the accepted answer just quoted non-normative notes from the Standard. Someone who knows the matter (@Jerry) is at least as good as a non-normative note (which are usually formulated slender and sometimes are even wrong), I deeply believe.
Johannes Schaub - litb
@Johannes - I upvoted Jerry too. I said I like to see the standard quoted - I never claimed to dislike explanation or even completely standard-free answers. In simple terms, there is no one perfect form for an answer - I almost always reject the one true way. But Matteos comment seemed to imply quoting the standard was bad, and I felt it was worth saying otherwise. Perhaps I could have worded it better, but I'm very bad at anticipating misunderstandings. People usually accuse me of being long-winded and pedantic when I try - and misinterpret the tone as arrogant etc. It's a no win thing.
Steve314
+2  A: 

As others have explained, the Standard says it's ok, and that the compiler can afford to be lenient about enforcing this because it doesn't affect the caller, but nobody's answered why the compiler should choose to be lenient. It's not particularly lenient in general, and a programmer who's just been looking at the interface then dives into the implementation may have it in the back of their mind that a parameter is const when it's not or vice versa - not a good thing.

This leniency allows implementation changes without modifying headers, which using traditional make tools triggers recompilation of client code. This can be a serious issue in enterprise scale development, where a non-substantive change in a low-level header (e.g. logging) can force rebuilding of virtually all objects between it and the applications... wasting thousands of hours of CPU time and delaying everyone and everything waiting on the builds.

So, it's ugly, but a practical concession.

Tony
I don't see it as leniancy. I can write a declaration and assignment `const int x = y;` even if y happens to be a non-const variable. It's important that the value of x stays constant in its lifetime - not that the value of y remain likewise constant. I can likewise write `int x = y;` even if y happens to be a constant. The parameter passing is doing much the same thing. It's not leniancy because there's no rule to be leniant about enforcing - the formal parameter is given a value (not a variable) by the call, and there's no reason why the source of that value should be non-modifiable.
Steve314
@Steve: the leniency aspect relates to accepting a declaration that states you will or will not be able to modify the parameter, then a definition that doesn't match. As you say, either would be fine, but that doesn't mean they're equivalent or consistent.
Tony
@Tony - In my mindset it does. A value simply doesn't have a `const` - it is a property of variables, members, parameters etc, but not of values. Values are always immutable anyway - 5 is always 5 and so on. This simple idea is very good for predicting how C++ will behave whether you're looking at variable initialisations, parameters and overloading, or whatever. In any case, parameters always behave very like local variables - only the initialization method differs, and that consistent with the rule above. For overloading to care about top-level const would seem very inconsistent to me.
Steve314