【问题标题】:Why isn't the compiler smarter in this const function overloading problem?为什么编译器在这个 const 函数重载问题中没有更聪明?
【发布时间】:2011-02-11 18:10:31
【问题描述】:

以下代码无法编译:

#include <iostream>
class Foo {
  std::string s;
 public:
  const std::string& GetString() const { return s; }
  std::string* GetString() { return &s; }
};

int main(int argc, char** argv){
  Foo foo;
  const std::string& s = foo.GetString(); // error
  return 0;
}

我收到以下错误:

const1.cc:11: error: 
invalid initialization of reference of type 'const std::string&' 
from expression of type 'std::string*

这确实有些道理,因为foo 不是const Foo 类型,而只是Foo,所以编译器想要使用非常量函数。但是,为什么它不能通过查看我分配给它的(类型)变量来识别我想调用 const GetString 函数?我发现这种情况令人惊讶。

【问题讨论】:

  • 如果您要抛出 insults,请准备好让编译器询问您为什么不够聪明,无法为您的 const 重载成员定义兼容的返回类型函数 ;-) 如果你想在非常量对象上调用函数的 const 版本,你 static_cast:const std::string &amp;s = static_cast&lt;const Foo&amp;&gt;(foo).GetString();
  • 实际上,现在我对编译器提出的一系列恶搞 StackOverflow 问题有了一个想法:“为什么我的程序员总是忘记将基类析构函数设为虚拟?”、“指针:为什么不能人类要么弄清楚它们,要么干脆停止使用它们?”,“我人类的代码总是无法编译,无论他尝试什么。我有错误吗?”等等。可能永远不会有任何意义。
  • @Steve:好主意。随意借用/修改/扩展我的答案中的代码 sn-p。

标签: c++ function constants overloading


【解决方案1】:

返回类型由实际调用的重载函数决定,它从不构成重载决议本身的一部分。 (如果没有使用返回类型怎么办?)

const 不是返回值的问题,因为您可以将非const 对象绑定到const 引用,这是您的函数返回一个您不取消引用的指针的事实。

由于foo 不是const,因此调用了非const GetString() - 它更适合非const 对象。你需要:

const std::string& s = *foo.GetString();

【讨论】:

    【解决方案2】:

    我不记得为什么他们不允许在返回类型上重载(我认为这是因为返回值可以被丢弃,因此函数不会是不同的),但是您可以使用 const_cast 提示解决问题给编译器: const std::string&amp; s = const_cast&lt;const Foo&amp;&gt;(foo).GetString();

    【讨论】:

    • 选择这个答案作为接受的答案,因为它实际上解释了为什么不能考虑返回值。
    • 认为这是因为它被认为是一个很好的属性,子表达式的类型不依赖于上下文(好吧,除非它影响名称解析) , 就在子表达式本身。实际上,在 C++ 中存在极少数例外情况,这与在您获取指向重载函数的指针时选择引用有关。
    • @Frank:它没有解释“为什么不能考虑返回值”。明显的反驳是,只有在使用返回值时,才应该考虑它。唯一真正的答案是这样更简单。将返回类型包含在重载决议中会使事情变得更加复杂。
    • 不能对返回类型进行重载的一个原因是名称的错位形式(链接器使用它来区分各种重载函数)不编码返回类型,只编码参数类型.我不确定如果更改名称修改以添加返回类型会破坏多少东西;可能所有 C++ 共享库(例如 boost)都需要一个主要版本号,因为这会破坏所有向后兼容性。 (所有名称都被破坏了,而不仅仅是重载。唯一的例外是extern "C" 声明的函数,它不能被重载。)
    • @Mike DeSimone:这不是循环论证吗?当然,如果语言允许对返回类型进行重载,则返回类型必须包含在名称修饰中。驱动名称修改系统的语言要求,而不是相反。
    【解决方案3】:

    扩展MarkB's answer 并稍微说明可能比丢弃返回值更糟糕的情况:

    #include <cmath>
    #include <complex>
    
    struct Foo {
       int x;
       double y;
       std::complex<char> z;
       // etc, etc
    };
    
    int evaluate(Foo f) { return f.x; }
    double evaluate(Foo f) { return f.y; }
    std::complex<char>(Foo f) { return f.z; }
    //etc, etc
    
    template <typename T> class Thingamajig
    {
    public:
        enum { value = sizeof (T); };
    };
    
    template <>
    class Thingamajig<double> class Thingamajig
    {
    public:
        int value(int a) { return a/3; }
    };
    
    template <typename T> Thingamajig<T> thingamatize(const T& t)
    {
        return Thingajamig<T>();
    }
    
    Foo myfoo = { 1, 2, 3 };
    size_t result = sizeof(thingamatize(std::abs(4 - evaluate(myfoo))).value('B'));
    

    确定应该选择double evaluate(Foo f) 是一个非常漫长的过程。

    另请阅读:http://blogs.msdn.com/ericlippert/archive/2006/03/31/delegates-lambdas-type-inference-and-long-playing-records.aspx

    “您今天想为您的编译器创建多少工作?™”

    【讨论】:

    • 我不明白这个例子。评估的重载将是模棱两可的,您必须通过强制转换来帮助编译器(如果允许返回类型的重载)。
    • 问题期望编译器根据上下文推断返回类型。该示例并不模棱两可(如果您仍然认为它是列出所有可能的匹配项),但编译器很难弄清楚。 C++ 中返回类型推断的一般情况等价于停机问题。
    • What about: size_t result = sizeof(thingamatize(std::abs(4.0f)).value('B')); 也没有弄清楚应该将abs的结果转换为double,所以一般问题似乎不是关于基于return的重载类型。 - 您的示例似乎只是表明不能期望编译器具有读心能力,而不是返回类型不能用于重载选择的技术原因。
    • 不会有结果的转换,因为编译器总是可以合成模板函数thingamatize来接受确切的类型。对于std::abs,返回类型与参数类型相同。正是其中一个 evaluate 重载使表达式有意义,但编译器很难弄清楚是哪个。
    • 为什么编译器必须想办法让它有意义?如我所见,有两种可能的结果。 4 - evaluate(myFoo) 本身是模棱两可的,因为int - intint - double 都可以。如果它强烈支持第一个,那么对abs(int) 的调用将是模棱两可的。 - 要使其选择您想要的重载,您必须这样做:4 - static_cast&lt;double&gt;(evaluate(myFoo));。您必须使用强制转换来指导编译器选择哪个重载,这种情况并不少见。
    【解决方案4】:

    选择重载时不使用函数的返回类型。这就是语言的工作原理。

    但是,隐式转换根据上下文选择的。所以,技术上你可以通过返回可以隐式转换为引用和指针的东西来编译它。

    #include <iostream>
    #include <string>
    
    struct Evil
    {
        std::string* p;
        Evil(std::string* p): p(p) {}
        operator std::string*() const { return p; }
        operator std::string&() const { return *p; }
    };
    
    struct ConstEvil
    {
        const std::string* p;
        ConstEvil(const std::string* p): p(p) {}
        operator const std::string*() const { return p; }
        operator const std::string&() const { return *p; }
    };
    
    class Foo {
        std::string s;
    public:
        ConstEvil GetString() const { return ConstEvil(&s); }
        Evil GetString() { return Evil(&s); }
    };
    
    int main(int argc, char** argv){
        Foo foo;
        const std::string& s = foo.GetString(); // ok
        return 0;
    }
    

    但真正的答案是,在 constness 上重载的函数应该具有类似的返回类型。 (我想这就是约定“对可变事物使用指针,对不可变事物使用常量引用”的地方。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-09
      • 2019-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多