10

Possible Duplicate:
Functions with const arguments and Overloading

I am pretty confused by the overloading and const declaration rules. Here are two things that puzzle me maybe you can help me find the deeper misunderstanding in my head that result in them being puzzling to me. ;)

First issue:

My compiler allows this:

void f(int & x) {
  std::cout << "plain f" << std::endl;
}
void f(const int & x) {
  std::cout << "const f" << std::endl;
}

But the following causes a compile error (function already has a body):

void f(int x) {
  std::cout << "plain f" << std::endl;
}
void f(const int x) {
  std::cout << "const f" << std::endl;
}

Which I suppose makes sense because I thought the const was only there to tell the compiler that the object being passed is not changed and in the second case it is copied anyway. But if that is correct then why can I overload functions using const?

In other words, why if I use the compiling version and call the functions like this:

  int x1 = 5;
  const int x2 = 5;
  f(x1);
  f(x2);

do I get "plain f" and "const f" instead of "const f" twice? Apparently now I am also using the const to tell the compiler which function to call not only that the reference doesn't change. This gets more confusing because if I remove the "plain" version it works just fine and calls the "const" version twice.

Now what is my actual question? I would like to know what the ideas behind this behavior are because otherwise memorizing it is very hard.

Community
  • 1
  • 1
Sarien
  • 6,647
  • 6
  • 35
  • 55
  • 1
    Top level `const` qualifiers on function parameters are _removed_ when determining a function's signature. `void f(int)` and `void f(const int)` refer to the same function. Top level `const` is not used for overload resolution. `const` further down a derived type (such as in `const int&`) do influence the types of function parameters and hence the function signature. – CB Bailey Jul 25 '12 at 08:40
  • See also: http://stackoverflow.com/questions/4212932/defining-a-function-with-different-signature – CB Bailey Jul 25 '12 at 08:48
  • @CharlesBailey Thanks for the links. Somehow I didn't find those, sorry. – Sarien Jul 25 '12 at 08:51

2 Answers2

5

n3337 13.1

[ Note: As specified in 8.3.5, function declarations that have equivalent parameter declarations declare the same function and therefore cannot be overloaded: ffer only in the presence or absence

— Parameter declarations that di3 of const and/or volatile are equivalent. That is, the const and volatile type-specifiers for each parameter type are ignored when determining which function is being declared, defined, or called. [ Example:

typedef const int cInt;
int f(int);
int f(const int); // redeclaration of f(int)
int f(int) { /* ... */ } // definition of f(int)
int f(cInt) { /* ... */ } // error: redefinition of f(int)

— end example ] Only the const and volatile type-specifiers at the outermost level of the parameter type specifica- tion 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.124 In particular, for any type T, “pointer to T,” “pointer to const T,” and “pointer to volatile T” are considered distinct parameter types, as are “reference to T,” “reference to const T,” and “reference to volatile T.”

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
ForEveR
  • 55,233
  • 2
  • 119
  • 133
  • I would still be interested in a reason for this behavior as it seems to be a confusing double meaning for const. (I would like to know, why it is defined like this in the standard). – Sarien Jul 25 '12 at 09:24
  • 2
    @CorporalTouchy: When you pass a parameter by value you create a copy. Whether or not that copy is `const` has no bearing on the behaviour for the calling code so there is no reason to distinguish between them on the calling side. – CB Bailey Jul 25 '12 at 09:37
2

I thought the const was only there to tell the compiler that the object being passed is not changed and in the second case it is copied anyway

You are correct. Because in the second case it's copied anyway, and so the const makes no difference to the caller, the standard defines that void f(const int x) and void f(int x) have the same signature. Hence they collide, you're trying to define the same function twice.

Because in the first case it isn't copied anyway, void f(const int &x) and void f(int &x) have different signatures. Hence they overload.

In your first case, it's forbidden to call the int& version of f with x2 as argument, because that would create a non-const reference to a const object without any explicit cast in sight. Doing that defeats the purpose of the const system (which is that if you want to break const-safety, you have to do so explicitly with a cast). So it makes sense to have const- and non-const overloads of functions with reference parameters.

In your second case, there's no relation between the const-ness of the source of a copy, and the const-ness of the destination. You can initialize a const variable from a non-const one, or a non-const one from a const one. This causes no problems and doesn't break const-safety. That's why the standard helpfully makes this clear by defining that your two "different" versions of f are actually the same function.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • So maybe my question boils down to: Why would I want to use the constless function (when calling)? To elaborate a bit: Why should I want to call f(int & x) when I have f(int const & x)? It seems to be hard to distinguish which one is called and entirely confusing. – Sarien Jul 25 '12 at 10:02
  • I suppose it relates to the fact that the "const" in my head tells me something about the behavior of the function (does not change this reference) and now it is being used to decide _which_ function to use instead. – Sarien Jul 25 '12 at 10:07
  • @CorporalTouchy: you *can't* call the non-const function with a const argument, because it's allowed to do something like `void f(int & x) { x = 6;}`. That's why the `f(const int & x)` overload gets selected when you pass it `x2`. When you pass it `x1`, the non-const overload gets selected, because in effect by providing a non-const lvalue as the argument, you're giving it permission to modify your variable. In fact your function body doesn't modify anything, so in this case sure, it's difficult to see why the difference matters. – Steve Jessop Jul 25 '12 at 11:54
  • I understand that, of course, but in the other direction I find the distinction weird: I am calling this function with a parameter that can be changed so the compiler decides I actually want it changed instead of using the function that doesn't change it. -- I give up. It must be in my head. (And after all this discussion I can probably remember how it works. ;) ) – Sarien Jul 25 '12 at 12:08
  • 1
    Well, if the const version is selected even when you give it a non-const parameter, then the non-const version is never going to get selected at all. So you could consider the selection rule to be a kindness to the non-const version, so it doesn't feel left out ;-) For a more practical example, consider `std::strchr`. That takes a pointer rather than a reference but it shows why the selection rules are useful -- if you pass it a `const char*` then you get a `const char*` back, whereas if you pass it a `char*` the non-const overload is chosen and you get a `char*` back. – Steve Jessop Jul 25 '12 at 12:29