【问题标题】:Overloading reference vs const reference重载引用 vs 常量引用
【发布时间】:2014-07-24 14:49:54
【问题描述】:

我有以下代码:

#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&amp;) 看起来可以同样很好地用于两个调用,为什么 const 版本明确地调用 f(b)

【问题讨论】:

  • 我正在阅读 C++ Primer 5th Edition atm。它在书中说 const 引用实际上是对常量变量的引用。因此,调用f(b) 其中bconst float,它将调用f() 的const 变体
  • 你的意思是 pass by value 而不是 reference 会产生歧义?
  • @AlexYan 好吧,它只是一个 const 引用,仅此而已,即您可以将非常量传递给它,但您将无法修改传递的对象。不知道是不是这个原因。
  • dansaks.com/articles/… 解释了为什么一个有效而另一个无效。重载必须以这种方式工作:如果参数是按值复制的,那么它是 const 对象的副本还是非常量对象的副本都没有区别,它是一个单独的对象。当参数是引用时,它是否引用 const 对象非常重要。

标签: c++ pass-by-reference


【解决方案1】:

给定两个相互竞争的重载,该标准要求编译器选择“最合适”的重载。 (如果没有唯一的最佳重载,或者无法访问唯一的最佳重载,则程序是非良构的。)

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

标准转换序列 S1 是比标准转换序列 S2 更好的转换序列,如果:

  • [...]

  • S1和S2是引用绑定(8.5.3),引用所引用的类型除了顶级的cv限定符外都是相同的类型,S2初始化的引用所引用的类型更多的是cv - 比由 S1 初始化的引用所引用的类型限定。

这是标准中给出的例子:

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&amp;T&amp; 更符合 cv 要求,因此按照标准,f(T&amp;)f(const T&amp;) 更合适,并通过重载决议选择。

【讨论】:

  • 好的,谢谢,我相信你,我很惊讶传递值不遵循相同的规则。在我看来,两者都应该是模棱两可的,但我没有写标准 :) 当然,按值传递 const 并没有多大意义,因为它只是暂时的,但仍然......
  • @vsoftco 考虑const 和非const 成员函数重载,它们分别采用隐含的const 引用和非const 引用对象参数。您不希望在非const 对象上调用这样的成员函数模棱两可,否则您首先会破坏允许这些重载的全部意义。
  • 好点(没想到这个)!然后我会让值传递遵循相同的规则,即明确。您是否有任何直觉为什么按值传递不遵循相同的规则?
  • @vsoftco 真的没有const 传值。顶级consts 在函数签名中被忽略。所以void foo(T t);void foo(const T t);的功能是一样的。
  • @juanchopanza,好的,这当然很有意义,如果这是编译时实际发生的情况,那么从您的评论和 TC 的评论来看,我想我明白发生了什么.
【解决方案2】:
  1. f(T&amp;)f(T const&amp;)
    这两个函数是不同的,因为第一个签名声明通过引用传递的任何变量都可以被函数修改。所以 const float 不能传递给第一个函数,而第二个是编译器唯一可行的选择。一个非常量变量可以传递给两者,因此编译器必须选择更合适的,如果有的话。标准规定,为了调用第二个函数,编译器必须将const 添加到任何非常量变量,而对于第一个函数,这不是必需的。添加 const 是一种隐式转换,与什么都不添加相比,它是一种“更糟糕”的转换(读作 more 转换步骤)。因此,标准要求编译器在传递非常量变量时​​选择第一个函数。
    如果您想知道:文字和临时变量不能绑定到非常量引用,所以f(4)f("meow")f(someFunc()) 都会调用第二个函数。

  2. f(T)f(const T)
    它们看起来不同,但不是在重载决议或函数签名方面。它们都是按值调用的,或者对于编译器:将参数的副本传递给函数。唯一的区别在于函数定义,您要求变量在函数体中是常量。任何函数声明都不会影响函数定义签名中的变量定义:

    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!
      //...
    }  
    

    这不是“ambuguos 调用类型错误”,因为对于编译器来说只有一个函数并且没有歧义。错误只是您确实定义了两次相同的功能。出于这个原因,在许多样式指南中,函数声明没有顶级 const 是首选,编译器通常会忽略它们,并且不会在错误或警告消息中提及它们。

【讨论】:

    猜你喜欢
    • 2011-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-12
    • 2013-07-31
    • 1970-01-01
    相关资源
    最近更新 更多