重载常量和非常量转换运算符返回数组类型时出现 MSVC 错误 C2593

MSVC error C2593 when overloading const and non-const conversion operator returning array type

本文关键字:常量 错误 C2593 类型 MSVC 数组 非常 转换 运算符 返回 重载      更新时间:2023-10-16

最近,我尝试使用转换运算符作为operator []的替代方法。

像下面的代码:

#include <iostream>
class foo
{
public:
using type = int[1];
public:
operator type       &()       { return data; }
operator type const &() const { return data; }
private:
type data;
};
int main()
{
foo f;
f[0] = 1;                        // error happens here
std::cout << f[0] << std::endl;
return 0;
}

我发现它可以在G ++中工作,但在MSVCv141(2017)中不起作用。

MSVC报告:

error C2593: 'operator [' is ambiguous
note: could be 'built-in C++ operator[(foo::type, int)'
note: or       'built-in C++ operator[(const foo::type, int)'
note: while trying to match the argument list '(foo, int)'

那么这是MSVC的错误还是其他什么?以及如何解决?

这是一个MSVC错误。候选人不是模棱两可的。我想解决方法是要么为数组提供一个命名转换函数,要么只提供一个用户定义的operator[]


使用运算符时,我们有 [over.match.oper]:

如果任一操作数的类型是类或枚举,则可能需要声明实现此运算符的用户定义运算符函数,或者可能需要用户定义的转换才能将操作数转换为适用于内置运算符的类型。

f[0]中,f具有类类型,因此我们考虑用户定义的运算符函数或用户定义的转换。没有前者,而是后者中的两个。两者都是可行的候选者:

f.operator type&()[1];       // ok
f.operator type const&()[1]; // also ok

因此,我们必须执行重载分辨率以选择最佳可行的候选者。两者之间的区别在于隐式对象参数。根据[over.match.funcs]:

为了使参数和参数列表在此异构集中具有可比性,成员函数被视为具有一个额外的参数,称为隐式对象参数,它表示已为其调用成员函数的对象。出于重载解析的目的,静态和非静态成员函数都具有隐式对象参数,但构造函数没有。

对于非静态成员函数,隐式对象参数的类型为

  • "对 cv X 的左值引用",用于声明没有 ref-qualifier 或 & ref-qualifier 的函数

[ ... ] 其中 X 是函数是其成员的类,cv 是成员函数声明上的 cv 限定。[ ... ]对于转换函数,该函数被视为隐含对象参数类的成员,目的是定义隐式对象参数的类型。

因此,这里的重载分辨率就像我们有:

type&       __op(foo& );       // #1
type const& __op(foo const& ); // #2
__op(f);

在这一点上,ICS排名规则告诉我们:

标准转换序列

S1 是比标准转换序列 S2 更好的转换序列,如果 [ ... ]S1 和 S2 是引用绑定,引用引用的类型是相同的类型,但顶级 cv 限定符除外,并且 S2 初始化的引用引用所引用的类型比 S1 初始化的引用所引用的类型更符合 cv 条件。

#1中的引用绑定比#2中的引用绑定更不符合 cv 条件,因此匹配度更高。由于这是一个更好的匹配,我们最终会得到一个独特的、最可行的函数,并且调用的格式良好。


此外,让转换运算符返回指针或对数组的引用在这里应该无关紧要。基本规则是相同的,但 MSVC 允许前者。MSVC 还允许这样做:

struct  foo
{
using type = int[1];
operator type&       ()       { return data; }
operator type const& () const { return data; }
type data;
};
void call(int (&)[1]); // #1
void call(int const (&)[1]); // #2
int main()
{
foo f;
call(f); // correctly calls #1
}