Поиск имени должен произойти первым. В данном случае для имени operator++
.
[basic.lookup] (выделено мое)
1 Правила поиска имен применяются одинаково ко всем именам (включая
typedef-names ([dcl.typedef]), namespace-names ([basic.namespace]),
и имена классов ([class.name])) везде, где грамматика допускает такие имена
в контексте обсуждается конкретное правило. Название поиска сотрудников
использование имени с объявлением ([basic.def]) этого имени. Имя
поиск должен найти однозначную декларацию для имени (см.
[Class.member.lookup]) . Поиск имени может связывать более одного
объявление с именем, если оно находит имя как имя функции;
говорят, что объявления формируют набор перегруженных функций
([Over.load]). Разрешение перегрузки ([over.match]) происходит после
поиск имени успешно завершен . Правила доступа (пункт [class.access])
считаются только один раз для поиска имени и разрешения перегрузки функции
(если применимо) успешно. Только после поиска имени, функция
Разрешение перегрузки (если применимо) и проверка доступа успешно завершены
атрибуты, представленные в объявлении имени, используются далее
в обработке выражений (пункт [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(); }
};
- конец примера]
Пример в значительной степени суммирует довольно длинные правила поиска в предыдущих абзацах [class.member.lookup]. В вашем коде есть неоднозначность. GCC правильно сообщить об этом.
Что касается обхода этого, люди в комментариях уже представили идеи для обхода проблемы. Добавить вспомогательный класс CRTP
template <class CRTP>
struct PrePost
: Pre<CRTP>
, Post<CRTP>
{
using Pre<CRTP>::operator++;
using Post<CRTP>::operator++;
};
struct Derived : PrePost<Derived> {};
Имя теперь находится в области действия одного класса и именует обе перегрузки. Поиск выполнен успешно, и разрешение перегрузки может продолжаться.