10

I have the following code:

#include <iostream>

template <typename T>
void f(T& x)        
{
    std::cout << "f(T& )" << std::endl; 
}

template <typename T>
void f(const T& x) 
{ 
    std::cout << "f(const T& )" << std::endl; 
}

int main() 
{
    int a = 0;
    const float b = 1.1;

    f(a); // call f(T&)
    f(b); // call f(const T&)
}

The output is:

f(T& )
f(const T& )

My question is: how does the compiler know which function to call? If I remove the references from the function definitions then I get an "ambiguous call" type of error, i.e. error: redefinition of 'f'. For me it looks like f(T&) can be equally well used for both calls, why is the const version unambiguously called for f(b)?

vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • I'm reading C++ Primer 5th Edition atm. It says in the book that a const reference is actually a reference to a constant variable. Therefore, calling `f(b)` where `b` is a `const float`, it would be calling the const variant of `f()` – Alex Jun 04 '14 at 04:51
  • you mean *pass by value* instead of *reference* creates ambiguity? – Rakib Jun 04 '14 at 04:55
  • @AlexYan Well it just means a `const` reference and nothing more, i.e. you can pass a non-const to it, but you will not be able to modify the passed object. Not sure this is the reason. – vsoftco Jun 04 '14 at 04:55
  • @RakibulHasan yes, exactly, passing by value creates ambiguity. I would have thought that both passing by value and reference would create ambiguity, but it looks like passing by reference is ok. – vsoftco Jun 04 '14 at 04:56
  • 1
    Related: http://stackoverflow.com/questions/17961719/overload-resolution-between-object-rvalue-reference-const-reference – David Heffernan Jun 04 '14 at 04:58
  • @DavidHeffernan thanks, still the explanation in the link is kind of "ad-hoc", and If I'd write the standard I wouldn't differentiate between pass-by-value and pass-by-reference in terms of overloading, it seems very artificial (at least I cannot find a good reason why one is working and the other one not) – vsoftco Jun 04 '14 at 05:05
  • 1
    http://www.dansaks.com/articles/2000-02%20Top-Level%20cv-Qualifiers%20in%20Function%20Parameters.pdf explains why one works and the other not. Overloading must work that way: if the argument is copied by value, it makes no difference whether it's a copy of a const object or a non-const object, it's a separate object. When the argument is a reference it is very important whether it refers to a const object or not. – Jonathan Wakely Jun 04 '14 at 19:10
  • @JonathanWakely thanks, I got the idea now, it makes sense, although at first seems quite arbitrary. – vsoftco Jun 05 '14 at 20:56

2 Answers2

10

Given two competing overloads, the standard requires the compiler to select the overload that has the "best fit". (If there's no unique best overload, or if the unique best overload is inaccessible, the program is ill-formed.)

In this case, the rules are provided by §13.3.3.2 [over.ics.rank]/p3:

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.

This is the example given in the standard:

int f(const int &);
int f(int &);
int g(const int &);
int g(int);
int i;
int j = f(i); // calls f(int &)
int k = g(i); // ambiguous

In your case, const T& is more cv-qualified than T&, so by the standard, f(T&) is a better fit than f(const T&) and is selected by overload resolution.

Community
  • 1
  • 1
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Ok, thanks, I believe you, I am just surprised that the pass-by-value doesn't follow the same rules. In my opinion, both should be ambiguous, but I didn't write the standard :) Of course passing a const by value doesn't make too much sense, as it is only a temporary, but still... – vsoftco Jun 04 '14 at 05:04
  • @vsoftco Consider `const` and non-`const` member function overloads, which take an implied `const` reference and non-`const` reference object parameter, respectively. You don't want to make calling such a member function on a non-`const` object ambiguous, or you'd defeat the whole point of allowing those overloads in the first place. – T.C. Jun 04 '14 at 05:09
  • good point (didn't think about this)! Then I'd make the pass-by-value follow the same rules, i.e. non-ambiguous. Do you have any kind intuition why pass by value doesn't follow the same rules? – vsoftco Jun 04 '14 at 05:13
  • 3
    @vsoftco There is no `const` pass by value really. Top level `consts` are ignored in function signatures. So `void foo(T t);` is the same function as `void foo(const T t);`. – juanchopanza Jun 04 '14 at 05:21
  • @juanchopanza, ok, this makes of course a lot of sense, and if this is what is actually happening at compile time then, from your comment and T.C.'s one I think I understand what's going on. – vsoftco Jun 04 '14 at 05:24
7
  1. f(T&) vs. f(T const&)
    The two functions are different, in that the first signature states that any variable passed by reference may be modified by the function. So the const float cannot be passed to the first function, and the second is the only viable choice for the compiler. A nonconst variable could be passed to both, so the compiler has to chose the better fit, if there is one. The standard says, that in order to call the second function, the compiler would have to add a const to any nonconst variable, while for the first function this is not necessary. Adding const is an implicit conversion, and it is a "worse" converison (read that as more conversion steps) than adding nothing. Therefore the standard demands that the compiler picks the first function when passing nonconst variables.
    In case you wonder: literals and temporaries can not be bound to nonconst references, so f(4), f("meow") and f(someFunc()) will all call the second function.

  2. f(T) vs. f(const T)
    They look different, but aren't in terms of overload resolution or function signature. Both of them are call by value, or for the compiler: pass a copy of the argument into the function. The only difference is in a function definition, that you require the variable to be constant in the function body. Any function declaration does not affect the variable definition in the function definition's signature:

    void f(int);          //a declaration
    void f(int i);        //redeclaration of the same function
    void f(int const);    //still the same function redeclared
    void f(int const i2); //yes... a redeclaration
    
    void f(int const i) { //at last a function definition and the copy of the argument used in the function body is required to be const
      //...
    } 
    
    void f(int i) {       //there is only one f, so this is a redefinition!
      //...
    }  
    

    This is not an "ambuguos call type error", because for the compiler there is only one function and no ambiguity. The error is simply that you did defin the same funciton twice. For that reason, it is preferred in many style guides that function declarations have no top-level const, and compilers will often ignore them and not mention them in error or warning messages.

Arne Mertz
  • 24,171
  • 3
  • 51
  • 90