Ответ 1
Поиск имени должен произойти первым. В данном случае для имени operator++
.
[basic.lookup] (акцент мой)
1 Правила поиска имен применяются одинаково ко всем именам (включая typedef-names ([dcl.typedef]), namespace-names ([basic.namespace]) и class-names ([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> {};
Имя теперь находится в области действия одного класса и именует обе перегрузки. Поиск успешен, и разрешение перегрузки может продолжаться.