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

I get these errors from 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++();
                ^~~~~~~~

Pre-decrement and post-decrement operators cause similar errors. No such errors with Clang. Any ideas what could be wrong or how to work around this?

Answer

Name lookup must occur first. In this case for the name 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]).

And only if the lookup is unambiguous, will overload resolution proceed. In this case, the name is found in the scope of two different classes, and so an ambiguity is present even prior to overload resolution.

[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 ]

The example pretty much summarizes the rather long lookup rules in the previous paragraphs of [class.member.lookup]. There is an ambiguity in your code. GCC is correct to report it.


As for working around this, people in comments already presented the ideas for a workaround. Add a helper CRTP class

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

struct Derived : PrePost<Derived> {};

The name is now found in the scope of a single class, and names both overloads. Lookup is successful, and overload resolution may proceed.

Attribution
Source : Link , Question Author : jotik , Answer Author : StoryTeller – Unslander Monica

Leave a Comment