GCC无法区分operator ++()和operator ++(int)

jot*_*tik 60 c++ operator-overloading multiple-inheritance ambiguous

template <typename CRTP>
struct Pre {
    CRTP & operator++();
};

template <typename CRTP>
struct Post {
    CRTP operator++(int);
};

struct Derived
    : Pre<Derived>
    , Post<Derived>
{};

int main() {
    Derived d;
    d++;
    ++d;
}
Run Code Online (Sandbox Code Playgroud)

我从GCC得到这些错误:

<source>: In function 'int main()':
<source>:18:10: error: request for member 'operator++' is ambiguous
        d++;
        ^~
<source>:8:14: note: candidates are: CRTP Post<CRTP>::operator++(int) [with CRTP = Derived]
        CRTP operator++(int);
            ^~~~~~~~
<source>:3:16: note:                 CRTP& Pre<CRTP>::operator++() [with CRTP = Derived]
        CRTP & operator++();
                ^~~~~~~~
<source>:19:11: error: request for member 'operator++' is ambiguous
        ++d;
        ^
<source>:8:14: note: candidates are: CRTP Post<CRTP>::operator++(int) [with CRTP = Derived]
        CRTP operator++(int);
            ^~~~~~~~
<source>:3:16: note:                 CRTP& Pre<CRTP>::operator++() [with CRTP = Derived]
        CRTP & operator++();
                ^~~~~~~~
Run Code Online (Sandbox Code Playgroud)

预减量和后减量运算符会导致类似的错误.Clang没有这样的错误.任何想法可能是错误的或如何解决这个问题?

Sto*_*ica 63

必须首先进行名称查找.在这种情况下的名称operator++.

[basic.lookup](强调我的)

1名查找规则均匀地适用于所有的名称(包括的typedef-名称([dcl.typedef]),命名空间的名称([basic.namespace])和类名([class.name]))的任何地方语法允许在特定规则讨论的上下文中的此类名称.名称查找将名称的使用与该名称的声明([basic.def])相关联.名称查找应该找到名称的明确声明(参见[class.member.lookup]).如果名称查找名称是函数名称,则名称查找可以将多个声明与名称相关联; 据说声明形成一组重载函数([over.load]).在名称查找成功后发生重载分辨率([over.match]).访问规则(Clause [class.access])仅在名称查找和功能重载解析(如果适用)成功时被认为是一次.只有在名称查找之后,函数重载解析(如果适用)和访问检查成功才会在表达式处理(Clause [expr])中进一步使用名称声明引入的属性.

并且只有在查找明确时,才会进行重载解析.在这种情况下,名称在两个不同类的范围内找到,因此即使在重载解析之前也存在歧义.

[class.member.lookup]

8如果明确找到重载函数的名称,则在访问控制之前也会发生重载分辨率([over.match]).通常可以通过使用类名限定名称来解决歧义.[例如:

struct A {
  int f();
};

struct B {
  int f();
};

struct C : A, B {
  int f() { return A::f() + B::f(); }
};
Run Code Online (Sandbox Code Playgroud)

- 结束例子]

该示例几乎总结了[class.member.lookup]之前段落中相当长的查找规则.您的代码存在歧义.海湾合作委员会报告是正确的.


至于解决这个问题,评论中的人已经提出了解决方法的想法.添加帮助程序CRTP类

template <class CRTP>
struct PrePost
    : Pre<CRTP>
    , Post<CRTP>
{
    using Pre<CRTP>::operator++;
    using Post<CRTP>::operator++;
};

struct Derived : PrePost<Derived> {};
Run Code Online (Sandbox Code Playgroud)

该名称现在位于单个类的范围内,并命名两个重载.查找成功,重载解析可能会继续.

  • Fwiw,MS VS2015(19.00.24215.1)通过IntelliNonsense抱怨模糊不清,但仍然编译代码成功(没有警告或错误)并在运行时执行希望的成员. (12认同)
  • @StoryTeller的确,对于"正常"方法`f()`与`f(int)`,`clang ++`抱怨:"错误:成员'f'在不同类型的多个基类中找到"并添加"注意" :成员通过模糊名称查找找到".GCC实际上在这里更加一致. (4认同)
  • 好.所以看起来这是一种语言中的缺陷(`d ++`和`++ d`指的是相同的运算符/函数名),gcc忠实地实现了,而clang和VS17似乎实现了用户的用途显然是有意的(甚至没有警告). (3认同)
  • @Walter - 差不多。如果我们在重载方面采取了像 Python 这样的方法,那么使用它可能会更容易。唉,选择了别的东西。 (2认同)
  • @PiotrNycz - 除了被特殊语法调用之外,重载运算符**是**常规函数.事实上,它们也可以像常规函数一样被调用.`d.operator ++(0)`会导致这种歧义,应该是`d ++`,因为这两者完全等价. (2认同)