【问题标题】:MSVC error C2593 when overloading const and non-const conversion operator returning array type重载 const 和非常量转换运算符返回数组类型时的 MSVC 错误 C2593
【发布时间】:2018-06-23 08:24:24
【问题描述】:

最近,我尝试使用转换运算符来替代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 的错误还是其他什么?以及如何解决?

【问题讨论】:

  • 您的错误在于假设语言考虑了f[0] 的结果将如何使用。简单地说,它没有。
  • @StoryTeller 似乎不是那个问题。上面的代码在 G++ 中工作。如果我使用operator int *() { return data; }operator int const *() const { return data; }。没有错误。
  • 很好,添加了语言律师标签。一般来说,“编译器 X 接受它”不是正确性的论据。如果这是 MSVC 实际上是正确的罕见情况,我不会感到惊讶,但只有时间和一些好的答案会证明一切。
  • @StoryTeller 你说得对,谢谢。而且我对C++标准不熟悉,希望有人能帮忙。

标签: c++ visual-c++ language-lawyer


【解决方案1】:

这是一个 MSVC 错误。候选人并不模棱两可。我想解决方法是提供一个 named 到数组的转换函数,或者只提供一个用户定义的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 ranking rules 告诉我们:

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

#1 中的引用绑定比 #2 中的引用绑定的 cv 限定更少,因此它是更好的匹配。由于这是一个更好的匹配,我们最终得到了一个独特的、最佳可行的函数,并且调用格式正确。


此外,让转换运算符返回指向数组的指针或引用在这里并不重要。基本规则相同,但 MSVC allows the former。 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
}

【讨论】:

  • 您错过了operator[](int*, std::ptrdiff_t)operator[](const int*, std::ptrdiff_t) 之间明确性的论点。
  • 太棒了!清晰易懂。如果有更好的走动,那就更好了。 (正如我提到的,我只是尝试将转换运算符作为运算符 [] 的替代。可能我的想法本身有点奇怪。命名转换函数似乎不太方便。)谢谢您的帮助。
  • @xskxzr 这有点额外的混乱。当然,从技术上讲,有 3 条路径 type&amp; --&gt; int*type&amp; --&gt; int const*type const&amp; --&gt; int const*,我只提到 (1) 比 (3) 好,而没有提到 (1) 比 (2) 好。但确实如此。
  • @Barry 我相信你的意思是f.operator type&amp;()[0]; // ok f.operator type const&amp;()[0]; // also ok 而不是f.operator type&amp;()[1]; // ok f.operator type const&amp;()[1]; // also ok
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-14
  • 2013-11-21
  • 1970-01-01
相关资源
最近更新 更多