GCC is actually correct here. If we follow the rules in [class.member.lookup] we first have
The lookup set for N in C, called S(N,C), consists of two component sets: the declaration set, a set of members named N; and the subobject set, a set of subobjects where declarations of these members were found (possibly via using-declarations). In the declaration set, type declarations (including injected-class-names) are replaced by the types they designate.
S(N,C) is calculated as follows:
So we are going to build a set of names S(N, C) for the class (also named C in this case)
Moving on to the next paragraph we have
The declaration set is the result of a single search in the scope of C for N from immediately after the class-specifier of C if P is in a complete-class context of C or from P otherwise. If the resulting declaration set is not empty, the subobject set contains C itself, and calculation is complete.
and when we do the lookup of operator()
in C
, we don't find any, so S(N, C)
is empty. We then move on to the next pargraph
Otherwise (i.e., C does not contain a declaration of N or the resulting declaration set is empty), S(N,C) is initially empty. Calculate the lookup set for N in each direct non-dependent ([temp.dep.type]) base class subobject Bi, and merge each such lookup set S(N,Bi) in turn into S(N,C).
So, we are going to go through each base and add it's lookup set into S(N, C)
which brings us to the next sub paragraph of
Otherwise, if the declaration sets of S(N,Bi) and S(N,C) differ, the merge is ambiguous: the new S(N,C) is a lookup set with an invalid declaration set and the union of the subobject sets. In subsequent merges, an invalid declaration set is considered different from any other.
So, since there was no operator()
in C
, but it is in the bases, then we now have invalid declaration set that contains the base class operator()
's. Then we have
he result of the search is the declaration set of S(N,T). If it is an invalid set, the program is ill-formed. If it differs from the result of a search in T for N from immediately after the class-specifier of T, the program is ill-formed, no diagnostic required.
Which tells us that a invalid declaration set is ill-formed, so it is an error and should fail to compile.
The workaround for this is to bring the base class functions into the derived class scope utilizing a using statement like
struct C : A, B {
using A::operator();
using B::operator();
};
Which now brings the name into C
which will give you a valid name set that can then be passed to overload resolution for it to pick the correct overload.