4

I have a question regarding the c++ function matching for parameters of types T and const T&. Let's say I have the following two functions:

void f(int i) {}
void f(const int &ri) {}

If I call f with an argument of type const int then this call is of course ambiguous. But why is a call of f with an argument of type int also ambiguous? Wouldn't be the first version of f be an exact match and the second one a worse match, because the int argument must be converted to a const int?

const int ci = 0;
int i = 0;
f(ci); // of course ambiguous
f(i); // why also ambiguous?

I know that such kind of overloading doesn't make much sense, because calls of f are almost always ambiguous unless the parameter type T doesn't have an accessible copy constructor. But I'm just studying the rules of function matching.

Regards, Kevin

EDIT: To make my question more clear. If I have the two functions:

void f(int *pi) {}
void f(const int *pi) {}

Then the following call is not ambiguous:

int i = 0;
f(&i); // not ambiguous, first version f(int*) chosen

Although both versions of f could be called with &i the first version is chosen, because the second version of f would include a conversion to const. That is, the first version is a "better match". But in the two functions:

void f(int i) {} and
void f(const int &ri) {}

This additional conversion to const seems to be ignored for some reason. Again both versions of f could be called with an int. But again, the second version of f would require a conversion to const which would make it a worse match than the first version f(int).

int i = 1;
// f(int) requires no conversion
// f(const int &) does require a const conversion
// so why are both versions treated as "equally good" matches?
// isnt this analogous to the f(int*) and f(const int*) example?
f(i); // why ambiguous this time?
sehe
  • 374,641
  • 47
  • 450
  • 633
KS85
  • 108
  • 5
  • Why do you think that `f(i)` is an exact match and `f(ci)` is not? – Ben Voigt Aug 23 '13 at 20:38
  • Because calling f(int) with an 'int' doesn't require any conversions. But calling f(const int &) would in my opinion require a conversion to 'const int&' because the lvalue-reference has a const target type. And because the second version of f would require such an additional conversion it would be a worse match than the first. – KS85 Aug 23 '13 at 20:46
  • Calling `f(const int&)` requires a qualification adjustment conversion, while calling `f(int)` requires an lvalue-to-rvalue conversion, and both are equal under the rules. – Ben Voigt Aug 23 '13 at 20:54
  • Also, my original comment was asking why you think that `f(ci)` isn't an exact match for `void f(int)`, since you said "`f(ci); // of course ambiguous`" – Ben Voigt Aug 23 '13 at 20:57

3 Answers3

4

One call involves an "lvalue-to-rvalue conversion", the other requires an identity conversion (for references) or a "qualification adjustment" (for pointers), and according to the Standard these are treated equally when it comes to overload resolution.

enter image description here

So, neither is better on the basis of differing conversions.

There is, however, a special rule in the Standard, section 13.3.3.2, that applies only if both candidates being compared take the parameter by reference.

Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if ... S1 and S2 are reference bindings (8.5.3), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers.

There's an identical rule for pointers.

Therefore the compiler will prefer

f(int*);
f(int&);

over

f(const int*);
f(const int&);

respectively, but there's no preference for f(int) vs f(const int) vs f(const int&), because lvalue-to-rvalue transformation and qualification adjustment are both considered "Exact Match".


Also relevant, from section 13.3.3.1.4:

When a parameter of reference type binds directly to an argument expression, the implicit conversion sequence is the identity conversion, unless the argument expression has a type that is a derived class of the parameter type, in which case the implicit conversion sequence is a derived-to-base Conversion.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Thanks a lot! This answers my question. Just wanted to note that f(int) is a redefinition of f(const int). – KS85 Aug 23 '13 at 20:56
  • I wasn't reading the standard. I was reading "C++ Primer 5th" by Lippman et. al. – KS85 Aug 23 '13 at 20:59
  • The other, `f(const int&)`, does not require a qualification conversion, nor is such a conversion applicable. It is a reference binding requiring No Conversion (first row). Qualification conversions are only for pointers and pointer-to-members. – Andrew Tomazos Aug 23 '13 at 23:08
  • @user1131467: That's based on the last excerpt I had quoted, right? – Ben Voigt Aug 23 '13 at 23:13
  • @BenVoigt: It's based on the definition of the Qualification Conversion (4.4), it cannot be applied to references, only pointers. The initialization of references is described in 8.5.3, an `int` expression binding to `const int&` is a direct binding with no conversions. Sometimes a reference binding does entail some standard conversions of the initializer to a temporary before binding, but not in this case. – Andrew Tomazos Aug 23 '13 at 23:17
  • @user1131467: It seems that three different cases are possible when binding a reference: identity conversion (for direct binding where the type matches and only cv-qualifiers may be added), derived-to-base conversion (when an implicit upcast occurs and then direct binding takes place), or lvalue-to-rvalue followed by either some promotion or user-defined conversion (when a temporary is created). – Ben Voigt Aug 23 '13 at 23:24
  • @user1131467: If you want to argue with the (draft) Standard, this is not a good place to do it. – Ben Voigt Aug 24 '13 at 13:59
1

The second call f(i) is also ambiguous because void f(const int &ri) indicates that ri is a reference to i and is a constant. Meaning it says that it will not modify the original i which is passed to that function.

The choice whether to modify the passed argument or not is in the hands of the implementer of the function not the client programmer who mearly uses that function.

Uchia Itachi
  • 5,287
  • 2
  • 23
  • 26
0

The reason the second call f(i) is ambiguous is because to the compiler, both functions would be acceptable. const-ness can't be used to overload functions because different const versions of functions can be used in a single cause. So in your example:

int i = 0;
fi(i);

How would the compiler know which function you intended in invoking? The const qualifier is only relevant to the function definition.

See const function overloading for a more detailed explanation.

Community
  • 1
  • 1
ssj_100
  • 149
  • 1
  • 11
  • 1
    The only `const` here is in the reference target type, which IS usable for overloading. Only top level `const` is ignored, and this question has no top level const. – Ben Voigt Aug 23 '13 at 20:37
  • 1
    The const qualifier isn't always only relevant for the function definition. What you mean is a "top level const qualifier". But this is not true for "low level const". For example: const T* is a low level const wheras T* const is a top level const. LValue References only have low-level const which is _never_ ignored. – KS85 Aug 23 '13 at 20:39