【问题标题】:c++ overload resolution and constnessc++ 重载决议和常量
【发布时间】:2017-07-27 14:02:25
【问题描述】:

(所有测试均在 Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24215.1 for x86 上执行)

考虑这个最小的例子:

struct myString
{
    operator const char *( ) const { return &dummy; }
    char& operator[]( unsigned int ) { return dummy; }
    const char& operator[]( unsigned int ) const { return dummy; }

    char dummy;
};

int main()
{
    myString str;
    const char myChar = 'a';

    if( str[(int) 0] == myChar ) return 0; //error, multiple valid overloads
}

根据重载解析规则(来自 cppreference)

如果隐式,F1 被确定为比 F2 更好的函数 F1 的所有参数的转换并不比隐式差 F2 的所有参数的转换,以及

1) 至少有一个 F1 的参数,其隐式转换优于 F2 的那个参数对应的隐式转换

2) 或。如果 不是那样,(仅在通过转换进行非类初始化的情况下), 从 F1 的返回类型到 正在初始化的类型优于标准转换序列 来自F2的返回类型

char& operator[]( unsigned int ) 应该更好,根据 1)。

这两个参数中(this = myString)根本不需要转换,operator const char *( ) const 将其转换为 const char*,const char& operator[]( unsigned int ) const 将其转换为 const myString,因此有一个参数没有任何隐式转换,这恰好是最好的转换

但是我的编译器会报以下错误:

1>  [///]\sandbox\sandbox\sandbox.cpp(29): error C2666: 'myString::operator []': 3 overloads have similar conversions
1>  [///]\sandbox\sandbox\sandbox.cpp(19): note: could be 'const char &myString::operator [](unsigned int) const'
1>  [///]\sandbox\sandbox\sandbox.cpp(18): note: or       'char &myString::operator [](unsigned int)'
1>  [///]\sandbox\sandbox\sandbox.cpp(29): note: while trying to match the argument list '(myString, int)'

另请注意,使用if( str[0u] == myChar ) return 0; 或删除operator const char *( ) const 可解决错误

为什么这里有错误,我对重载解析规则有什么误解?

编辑:这可能是这个版本中的一个视觉 C++ 错误,对此有任何明确的确认吗?

【问题讨论】:

  • 用 gcc/clang here 编译。
  • 编译器声称有三个候选重载,但只提到其中两个,这让我有点惊讶。
  • 有趣。我有版本 19.10.25019,它对我来说编译得很好。可能只是他们修复的一个错误。
  • 它是可视化 C++ 编译器,我也花了一些时间在 clang 上进行了测试,它确实编译得很好
  • @nathanoliver ,这可能是我的版本中的一个错误,我会尝试检查补丁说明(编辑:如果我找到那些)

标签: c++ language-lawyer overload-resolution


【解决方案1】:

这是问题的缩小版,在 all compilers I threw at it 上重现。

#include <stddef.h>

struct myString
{
    operator char *( );
    char& operator[]( unsigned ptrdiff_t );
};

int main()
{
    myString str;
    if( str[(ptrdiff_t) 0] == 'a' ) return 0; //error, multiple valid overloads
}

基本上,您有两个候选函数来获取charbool operator==(char,char)[over.match.oper]/3

请注意,如果myString::operator[] 使用ptrdiff_t 而不是unsigned ptrdiff_t,那么它会根据[over.built]/1 隐藏内置运算符。因此,如果您只想避免此类问题,只需确保任何采用整数值的 operator[] 重载都采用 ptrdiff_t

我将跳过生存能力检查[over.match.viable],直接进入转化排名。

char&amp; myString::operator[]( unsigned ptrdiff_t )

对于重载,this被认为有一个前导的隐式对象参数,所以要匹配的签名是 (myString&amp;, unsigned ptrdiff_t)

myString&amp; => myString&amp;

标准转换序列:Identity(等级:完全匹配)-直接绑定引用

ptrdiff_t => unsigned ptrdiff_t

标准转换序列:Lvalue Transformation -> Integral conversion(等级:转换) - 有符号左值到无符号纯右值

char&amp; operator[]( char*, ptrdiff_t)

myString&amp; => char*

User-defined conversion sequence: Identity + operator char*(myString&amp;)

请注意,根据[over.match.oper]/7,我们不会获得第二个标准转换序列。

ptrdiff_t => ptrdiff_t

标准转换顺序:身份(等级:完全匹配

Best viable function

第一个参数

标准转换序列优于用户自定义转换序列 ([over.ics.rank]/2.1)

第二个参数

排名转换不如排名精确匹配 ([over.ics.rank]/3.2.2)

结果

我们无法满足requirement

如果对于所有参数 i,ICSi(F1) 不是比 ICSi(F2) 更差的转换序列

所以这两个函数都不是更好的函数。

因此,对于[over.match.best]/2,它是模棱两可的


如何解决这个问题?

嗯,最简单的解决方案是永远不要operator[]重载的参数可以通过精确匹配以外的其他方式从ptrdiff_t转换为em> 排序的转换。查看the conversions table 似乎意味着您应该始终将您的operator[] 成员函数声明为X&amp; T::operator[]( ptrdiff_t )。这涵盖了“像数组一样行动”的常见用例。如上所述,精确地使用 ptrdiff_t 甚至会通过取消内置的下标运算符来抑制 搜索 operator T* 候选者。

另一个选项是不为类定义T1 operator[]operator T2*,其中T1T2 可能都满足(可能是隐式的)函数调用的相同参数。这涵盖了您将operator[] 用于巧妙句法的情况,并最终使用T T::operator[](X) 之类的情况。例如,如果X::operator ptrdiff_t() 存在,T::operator T*() 也存在,那么你又模棱两可了。

我可以想象T::operator T*() 的唯一用例是,如果您希望您的类型隐式转换为指向自身的指针,例如函数。 不要那样做...

【讨论】:

  • 很好的调查!
  • 谢谢。一旦我意识到发生了什么,我就意识到这是一种更常见、更令人恼火的模棱两可的情况:当你有多个参数时,你的不同重载在其中一个上都比其他重载要好。我相信一般的解决方案是“添加一个更好地匹配所有参数的重载”。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-02-25
  • 1970-01-01
  • 2011-03-26
  • 2021-07-05
  • 1970-01-01
  • 2015-03-29
  • 2014-04-20
相关资源
最近更新 更多