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

GCC can't differentiate between operator++() and operator++(int)

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;
}

我从 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++();
                ^~~~~~~~

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

必须首先进行名称查找。在本例中为名称 operator++.

[basic.lookup] (emphasis mine)

1 The name lookup rules apply uniformly to all names (including typedef-names ([dcl.typedef]), namespace-names ([basic.namespace]), and class-names ([class.name])) wherever the grammar allows such names in the context discussed by a particular rule. Name lookup associates the use of a name with a declaration ([basic.def]) of that name. Name lookup shall find an unambiguous declaration for the name (see [class.member.lookup]). Name lookup may associate more than one declaration with a name if it finds the name to be a function name; the declarations are said to form a set of overloaded functions ([over.load]). Overload resolution ([over.match]) takes place after name lookup has succeeded. The access rules (Clause [class.access]) are considered only once name lookup and function overload resolution (if applicable) have succeeded. Only after name lookup, function overload resolution (if applicable) and access checking have succeeded are the attributes introduced by the name's declaration used further in expression processing (Clause [expr]).

并且只有当查找明确时,重载解析才会继续。在这种情况下,名称在两个不同的 class 范围内找到,因此甚至在重载解析之前就存在歧义。

[class.member.lookup]

8 If the name of an overloaded function is unambiguously found, overloading resolution ([over.match]) also takes place before access control. Ambiguities can often be resolved by qualifying a name with its class name. [ Example:

struct A {
  int f();
};

struct B {
  int f();
};

struct C : A, B {
  int f() { return A::f() + B::f(); }
};

— end example ]

该示例几乎总结了 [class.member.lookup] 前几段中相当长的查找规则。您的代码中存在歧义。 GCC 报告是正确的。


至于解决这个问题,评论中的人已经提出了解决方法的想法。添加辅助 CRTP class

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

struct Derived : PrePost<Derived> {};

现在可以在单个 class 的范围内找到该名称,并为两个重载命名。查找成功,可以继续进行重载解析。