重载引用 vs 常量引用

10

我有以下代码:

#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&)
}

输出结果为:

f(T& )
f(const T& )
我的问题是:编译器如何知道调用哪个函数?如果我从函数定义中删除了引用,那么就会出现"模糊的调用"类型的错误,即 error: redefinition of 'f'。对我来说,看起来f(T&)可以同样适用于两种调用,为什么const版本在f(b)中被明确地调用?

我目前正在阅读C++ Primer第五版。书中提到,const引用实际上是对一个常量变量的引用。因此,当调用f(b),其中b是一个const float时,它将调用f()的const变体。 - Alex
@RakibulHasan 是的,没错,按值传递会产生歧义。我本以为按值和引用传递都会产生歧义,但看起来按引用传递是可以的。 - vsoftco
1
相关:https://dev59.com/_GMl5IYBdhLWcg3w_bHj - David Heffernan
@DavidHeffernan 谢谢,但是链接中的解释有点“临时抱佛脚”的感觉。如果我写标准的话,我不会在重载方面区分按值传递和按引用传递,这似乎非常人为(至少我找不到一个好的理由来解释为什么一个可以工作而另一个不行)。 - vsoftco
1
http://www.dansaks.com/articles/2000-02%20Top-Level%20cv-Qualifiers%20in%20Function%20Parameters.pdf解释了为什么一个有效而另一个无效。重载必须按照这种方式工作:如果参数是通过值复制的,则将const对象或非const对象的副本复制并没有区别,它们是单独的对象。当参数是引用时,它引用const对象还是非const对象非常重要。 - Jonathan Wakely
显示剩余3条评论
2个回答

10

给定两个竞争的重载函数,标准要求编译器选择最匹配的函数。(如果没有唯一最佳的重载函数,或者唯一最佳的重载函数无法访问,则程序是非法的。)

在这种情况下,规则由§13.3.3.2 [over.ics.rank]/p3提供:

如果标准转换序列S1比标准转换序列S2更好,则标准转换序列S1是更好的转换序列。条件如下:

  • [...]

  • S1和S2是引用绑定(8.5.3),并且这些引用所引用的类型除了顶层cv限定符外相同,而且由S2初始化的引用所引用的类型比S1初始化的引用所引用的类型更具有cv限定符。

这是标准中提供的示例:

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

在您的情况下,const T&T& 更具有cv限定符,因此根据标准,f(T&)f(const T&) 更适合,并且会被重载解析所选中。


好的,谢谢,我相信你,我只是惊讶于传值方式没有遵循相同的规则。在我看来,两者都应该是模棱两可的,但我没有编写标准 :) 当然,按值传递一个const并没有太多意义,因为它只是一个临时的东西,但还是... - vsoftco
@vsoftco 考虑使用const和非const成员函数重载,分别采用隐式的const引用和非const引用对象参数。您不希望在非const对象上调用此类成员函数时产生歧义,否则您将打败允许这些重载的初衷。 - T.C.
好观点(没想到这个)!那么我会让传值遵循相同的规则,即非歧义。你有什么直觉,为什么传值不遵循相同的规则? - vsoftco
3
实际上不存在通过值传递的常量。在函数签名中,顶层const被忽略。因此,void foo(T t);void foo(const T t);是相同的函数。 - juanchopanza
@juanchopanza,好的,这当然很有意义,如果这确实是在编译时发生的事情,那么从你的评论和T.C.的评论中,我认为我理解了正在发生的事情。 - vsoftco

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.


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接