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

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.