当重载const和非const转换运算符返回数组类型时,MSVC错误C2593

MYL*_*YLS 8 c++ visual-c++ language-lawyer

最近,我尝试使用转换运算符作为替代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;
}
Run Code Online (Sandbox Code Playgroud)

我发现它适用于G ++,不适用于MSVC v141(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)'
Run Code Online (Sandbox Code Playgroud)

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

Bar*_*rry 4

这是一个 MSVC 错误。候选人的立场并不含糊。我猜想的解决方法是要么为数组提供一个命名转换函数,要么只提供一个用户定义的operator[].

\n
\n

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

\n
\n

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

\n
\n

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

\n
f.operator type&()[1];       // ok\nf.operator type const&()[1]; // also ok\n
Run Code Online (Sandbox Code Playgroud)\n

因此,我们必须执行重载决策来选择最佳可行的候选者。两者的区别在于隐式对象参数。根据[over.match.funcs]

\n
\n

为了使实参和形参列表在这个异构集合中具有可比性,成员函数被认为有一个额外的参数,称为隐式对象形参,它表示调用该成员函数的对象。出于重载决策的目的,静态和非静态成员函数都具有隐式对象参数,但构造函数没有。[...]

\n

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

\n
    \n
  • \xe2\x80\x9clvalue 对 cv X\xe2\x80\x9d 的引用,用于不带引用限定符或使用 & 引用限定符声明的函数
  • \n
\n

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

\n
\n

所以这里的重载解析的行为就好像我们有:

\n
type&       __op(foo& );       // #1\ntype const& __op(foo const& ); // #2\n\n__op(f);\n
Run Code Online (Sandbox Code Playgroud)\n

此时,ICS排名规则告诉我们:

\n
\n

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

\n
\n

中的参考绑定#1比 中的参考绑定的简历限定程度更低#2,因此它是更好的匹配。由于这是一个更好的匹配,我们最终得到了一个独特的、最佳可行的函数,并且调用的格式良好。

\n
\n

此外,让转换运算符返回指针或对数组的引用在这里并不重要。底层规则是相同的,但 MSVC允许前者。MSVC 也允许这样做:

\n
struct  foo\n{\n    using type = int[1];\n    operator type&       ()       { return data; }\n    operator type const& () const { return data; }\n    type data;\n};\n\nvoid call(int (&)[1]); // #1\nvoid call(int const (&)[1]); // #2\n\nint main()\n{\n    foo f;\n    call(f); // correctly calls #1\n}\n
Run Code Online (Sandbox Code Playgroud)\n