Overload resolution for multiply inherited operator() - c++

First, consider this C++ code:
#include <stdio.h>
struct foo_int {
void print(int x) {
printf("int %d\n", x);
}
};
struct foo_str {
void print(const char* x) {
printf("str %s\n", x);
}
};
struct foo : foo_int, foo_str {
//using foo_int::print;
//using foo_str::print;
};
int main() {
foo f;
f.print(123);
f.print("abc");
}
As expected according to the Standard, this fails to compile, because print is considered separately in each base class for the purpose of overload resolution, and thus the calls are ambiguous. This is the case on Clang (4.0), gcc (6.3) and MSVC (17.0) - see godbolt results here.
Now consider the following snippet, the sole difference of which is that we use operator() instead of print:
#include <stdio.h>
struct foo_int {
void operator() (int x) {
printf("int %d\n", x);
}
};
struct foo_str {
void operator() (const char* x) {
printf("str %s\n", x);
}
};
struct foo : foo_int, foo_str {
//using foo_int::operator();
//using foo_str::operator();
};
int main() {
foo f;
f(123);
f("abc");
}
I would expect the results to be identical to the previous case, but it is not the case - while gcc still complains, Clang and MSVC can compile this fine!
Question #1: who is correct in this case? I expect it to be gcc, but the fact that two other unrelated compilers give a consistently different result here makes me wonder whether I'm missing something in the Standard, and things are different for operators when they're not invoked using function syntax.
Also note that if you only uncomment one of the using declarations, but not the other, then all three compilers will fail to compile, because they will only consider the function brought in by using during overload resolution, and thus one of the calls will fail due to type mismatch. Remember this; we'll get back to it later.
Now consider the following code:
#include <stdio.h>
auto print_int = [](int x) {
printf("int %d\n", x);
};
typedef decltype(print_int) foo_int;
auto print_str = [](const char* x) {
printf("str %s\n", x);
};
typedef decltype(print_str) foo_str;
struct foo : foo_int, foo_str {
//using foo_int::operator();
//using foo_str::operator();
foo(): foo_int(print_int), foo_str(print_str) {}
};
int main() {
foo f;
f(123);
f("foo");
}
Again, same as before, except now we don't define operator() explicitly, but instead get it from a lambda type. Again, you'd expect the results to be consistent with the previous snippet; and this is true for the case where both using declarations are commented out, or if both are uncommented. But if you only comment out one but not the other, things are suddenly different again: now only MSVC complains as I would expect it to, while Clang and gcc both think it's fine - and use both inherited members for overload resolution, despite only one being brought in by using!
Question #2: who is correct in this case? Again, I'd expect it to be MSVC, but then why do both Clang and gcc disagree? And, more importantly, why this is different from the previous snippet? I would expect the lambda type to behave exactly the same as a manually defined type with overloaded operator()...

Barry got #1 right. Your #2 hit a corner case: captureless nongeneric lambdas have an implicit conversion to function pointer, which got used in the mismatch case. That is, given
struct foo : foo_int, foo_str {
using foo_int::operator();
//using foo_str::operator();
foo(): foo_int(print_int), foo_str(print_str) {}
} f;
using fptr_str = void(*)(const char*);
f("hello") is equivalent to f.operator fptr_str()("hello"), converting the foo to an pointer-to-function and calling that. If you compile at -O0 you can actually see the call to the conversion function in the assembly before it gets optimized away. Put an init-capture in print_str, and you'll see an error since the implicit conversion goes away.
For more, see [over.call.object].

The rule for name lookup in base classes of a class C only happens if C itself doesn't directly contain the name is [class.member.lookup]/6:
The following steps define the result of merging lookup set S(f,Bi)
into the intermediate S(f,C):
If each of the subobject members of S(f,Bi) is a base class subobject of at least one of the subobject members of S(f,C), or if S(f,Bi) is empty, S(f,C) is unchanged and the merge is complete. Conversely, if each of the subobject members of S(f,C) is a base class subobject of at least one of the subobject members of S(f,Bi), or if S(f,C) is empty, the new S(f,C) is a copy of S(f,Bi).
Otherwise, if the declaration sets of S(f,Bi) and S(f,C) differ, the merge is ambiguous: the new S(f,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.
Otherwise, the new S(f,C) is a lookup set with the shared set of declarations and the union of the subobject sets.
If we have two base classes, that each declare the same name, that the derived class does not bring in with a using-declaration, lookup of that name in the derived class would run afoul of that second bullet point and the lookup should fail. All of your examples are basically the same in this regard.
Question #1: who is correct in this case?
gcc is correct. The only difference between print and operator() is the name that we're looking up.
Question #2: who is correct in this case?
This is the same question as #1 - except we have lambdas (which give you unnamed class types with overload operator()) instead of explicit class types. The code should be ill-formed for the same reason. At least for gcc, this is bug 58820.

Your analysis of the first code is incorrect. There is no overload resolution.
The name lookup process occurs wholly before overload resolution. Name lookup determines which scope an id-expression is resolved to.
If a unique scope is found via the name look-up rules, then overload resolution begins: all instances of that name within that scope form the overload set.
But in your code, name lookup fails. The name is not declared in foo, so base classes are searched. If the name is found in more than one immediate base class then the program is ill-formed and the error message describes it as an ambiguous name.
The name lookup rules do not have special cases for overloaded operators. You should find that the code:
f.operator()(123);
fails for the same reason as f.print failed. However, there is another issue in your second code. f(123) is NOT defined as always meaning f.operator()(123);. In fact the definition in C++14 is in [over.call]:
operator() shall be a non-static member function with an arbitrary number of parameters. It can have default arguments. It implements the function call syntax
postfix-expression ( expression-list opt )
where the postfix-expression evaluates to a class object and the possibly empty expression-list matches the parameter list of an operator() member function of the class. Thus, a call x(arg1,...) is interpreted as x.operator()(arg1, ...) for a class object x of type T if T::operator()(T1, T2, T3) exists and if the operator is selected as the best match function by the overload resolution mechanism (13.3.3).
This actually seems an imprecise specification to me so I can understand different compilers coming out with different results. What is T1,T2,T3? Does it mean the types of the arguments? (I suspect not). What are T1,T2,T3 when multiple operator() function exist, only taking one argument?
And what is meant by "if T::operator() exists" anyway? It could perhaps mean any of the following:
operator() is declared in T.
Unqualified lookup of operator() in the scope of T succeeds and performing overload resolution on that lookup set with the given arguments succeeds.
Qualified lookup of T::operator() in the calling context succeeds and performing overload resolution on that lookup set with the given arguments succeeds.
Something else?
To proceed from here (for me anyway) I would like to understand why the standard didn't simply say that f(123) means f.operator()(123);, the former being ill-formed if and only if the latter is ill-formed. The motivation behind the actual wording might reveal the intent and therefore which compiler's behaviour matches the intent.

Related

Overload resolution for operator functions

Why does this code not compile ? I would have expected that the built-in unary operator& would have been selected, such that the ambiguity between the two binary operator& should not matter. If you comment out one of the binary operator&, the code compiles (with gcc 11.2) and the built-in unary operator& is selected.
struct A
{ int operator&(int) const { return 1; }
};
struct B
{ int operator&(int) const { return 2; }
};
struct C : A, B
{};
C* test(C& c)
{ return &c; } //error: request for member 'operator&' is ambiguous
Problem is overload resolution. First function name should be matched and if abutting is found error should be reported. After that matching of arguments is performed. Here is an example with regular function: https://godbolt.org/z/nYfjdn1Td
As you can see ambiguity is reported for foo on all compilers even although both foos have different number of arguments.
Same issue should pop up for operator& so more like gcc is right here.
Overload resolution is a complex topic. Usually you do not have to think about it. Here is nice source which explains how complex this is.
If you wish to fix problem and have binary operator available for such code, you can do this:
struct C : A, B
{
using A::operator&;
using B::operator&;
};
This fixes problem with your test on all compilers.
of course ambiguity for instaceOfC & intValue remains, but you can resolve it by doping one using statement.
https://godbolt.org/z/ds3W4GYcT
This is due to unqualified name lookup.
Initially, name lookup begins in the scope of class C. No declarations of operator& are found.
Then look up proceeds to base classes in turn. In each of A and B there is a different declaration with the name operator&.
At this point the set of declarations found by name lookup is A::operator& and B::operator&. These are considered ambiguous at the name lookup stage so overload resolution, where the difference between unary and binary operator& would be discovered, is never reached.
See the example at cppreference for unqualified name look up for more info.
If only one of A or B was inherited then the name lookup set wouldn't be member function name lookup set wouldn't be ambiguous. It would proceed to overload resolution an correctly deduce unary operator&.
Adding a using declaration to the body of class C, bring them both into the scope C:: for name lookup..
struct C : A, B
{
using A::operator&;
using B::operator&;
};
The scope of C is searched before base classes so the ambiguous step is never reached.

Static and non-static member function templates with the same parameter types and requires clause in C++

Static and non-static member functions with the same parameter types cannot be overloaded. But if member functions are templates and one of them has requires clause then all compilers allow it. But the problems appear when one calls both of the member functions:
struct A {
static int f(auto) { return 1; }
int f(auto) requires true { return 2; }
};
int main() {
[[maybe_unused]] int (A::*y)(int) = &A::f; // ok everywhere (if no below line)
[[maybe_unused]] int (*x)(int) = &A::f; //ok in GCC and Clang (if no above line)
}
If only one (any) line is left in main() then GCC and Clang accept the program. But when both lines in main() are present, Clang prints
error: definition with same mangled name '_ZN1A1fIiEEiT_' as another definition
and GCC reports an internal compiler error. Demo: https://gcc.godbolt.org/z/4c1z7fWvx
Are all compilers wrong in accepting struct A definition with overloaded static and not-static member functions? Or they just have similar bugs in the calling of both functions.
This is quite messy! While skimming through the standard, I found this much simpler example in [over.over]:
struct X {
int f(int);
static int f(long);
};
int (X::*p1)(int) = &X::f; // OK
int (*p2)(int) = &X::f; // error: mismatch
int (*p3)(long) = &X::f; // OK
So I first tried the two valid lines, but even they are rejected (independently of each other) by all three compilers. That is a problem.
Looking further, about the address of function templates, [temp.deduct.funcaddr] says:
Template arguments can be deduced from the type specified when taking the address of an overload set.
If there is a target, the function template's function type and the target type are used as the types of P and A, and the deduction is done as described in [temp.deduct.type].
Otherwise, deduction is performed with empty sets of types P and A.
Such targets are described in [over.over], and in your example are of the first type:
(1.1)
an object or reference being initialized ([dcl.init], [dcl.init.ref], [dcl.init.list]),
Even before considering any constraints, it seems to me that a compiler should be able to identify a unique candidate in both of your cases, just as in the simpler examples, given that the targets have different signatures.
According to the latest draft of C++20 standard, class.static.mfct#2:
There shall not be a static and a non-static member function with the same name and the same parameter types ([over.load]).
There is no exception here for the presence of requires-clause to differentiate member functions, only same name and the same parameter types. So the definition of struct A is malformed in C++20.
The same item was rephrased In the first draft of C++23, class.static.mfct#2
There cannot be a static and a non-static member function with the same name, parameter-type-list, and trailing requires-clause ([over.load]).
According to this, the definition of A is already fine. And it looks like GCC, Clang, and MSVC all follow this statement even in C++20 mode. And the errors we observe happen because of same mangled names of both functions (which hardly can be fixed preserving the current ABI).
And in the latest draft of C++, class.static.mfct#2 any restriction on having static and not-static member functions with same name and parameters are removed.

operator[] lookup into template base classes

The following code is causing a little headache for us: clang and MSVC accepts the following code, while GCC rejects it. We believe GCC is right this time, but I wanted to make it sure before filing the bugreports. So, are there any special rules for operator[] lookup that I'm unaware of?
struct X{};
struct Y{};
template<typename T>
struct B
{
void f(X) { }
void operator[](X){}
};
template<typename T>
struct C
{
void f(Y) { }
void operator[](Y){}
};
template<typename T> struct D : B<T>, C<T> {};
int main()
{
D<float> d;
//d.f(X()); //This is erroneous in all compilers
d[Y()];//this is accepted by clang and MSVC
}
So is the above code is correct in resolving the operator[] call in the main function?
It's not 100% clear in which compiler the issue lie. The standard goes over a lot of rules for name lookup (which is what this is an issue with), but more specifically section 13.5.5 covers the operator[] overload:
13.5.5 Subscripting [over.sub]
1 - operator[] shall be a non-static member function with exactly one parameter. It implements the subscripting syntax
postfix-expression [ expr-or-braced-init-list ]
Thus, a subscripting expression x[y] is interpreted as x.operator[](y) for a class object x of type T if T::operator[](T1) exists and if the operator is selected as the best match function by the overload resolution mechanism (13.3.3).
Looking at the standard on Overloading (chapter 13):
13 Overloading [over]
1 - When two or more different declarations are specified for a single name in the same scope, that name is said to be overloaded. By extension, two declarations in the same scope that declare the same name but with different types are called overloaded declarations. Only function and function template declarations can be overloaded; variable and type declarations cannot be overloaded.
2 - When an overloaded function name is used in a call, which overloaded function declaration is being referenced is determined by comparing the types of the arguments at the point of use with the types of the parameters in the overloaded declarations that are visible at the point of use. This function selection process is called overload resolution and is defined in 13.3.
...
13.2 Declaration matching [over.dcl]
1 - Two function declarations of the same name refer to the same function if they are in the same scope and have equivalent parameter declarations (13.1). A function member of a derived class is not in the same scope as a function member of the same name in a base class.
So according to this and section 10.2 on derived classes, since you've declared struct D : B, C, both B and C have member functions for operator[] but different types, thus the operator[] function is overloaded within the scope of D (since there's no using nor is operator[] overridden or hidden directly in D).
Based on this, MSVC and Clang are incorrect in their implementations since d[Y()] should be evaluated to d.operator[](Y()), which would produce an ambiguous name resolution; so the question is why do they accept the syntax of d[Y()] at all?
The only other areas I could see with regards to the subscript ([]) syntax make reference to section 5.2.1 (which states what a subscript expression is) and 13.5.5 (stated above), which means that those compilers are using other rules to further compile the d[Y()] expression.
If we look at name lookup, we see that 3.4.1 Unqualified name lookup paragraph 3 states that
The lookup for an unqualified name used as the postfix-expression of a function call is described in 3.4.2.
Where 3.4.2 states:
3.4.2 Argument-dependent name lookup [basic.lookup.argdep]
1 - When the postfix-expression in a function call (5.2.2) is an unqualified-id, other namespaces not considered during the usual unqualified lookup (3.4.1) may be searched, and in those namespaces, namespace-scope friend function or function template declarations (11.3) not otherwise visible may be found.
2 - For each argument type T in the function call, there is a set of zero or more associated namespaces and a set of zero or more associated classes to be considered. The sets of namespaces and classes is determined entirely by the types of the function arguments (and the namespace of any template template argument). Typedef names and using-declarations used to specify the types do not contribute to this set. The sets of namespaces and classes are determined in the following way:
...
(2.2) - If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the innermost enclosing namespaces of its associated classes. Furthermore, if T is a class template specialization, its associated namespaces and classes also include: the namespaces and classes associated with the types of the template arguments provided for template type parameters (excluding template template parameters); the namespaces of which any template template arguments are members; and the classes of which any member templates used as template template arguments are members. [ Note: Non-type template arguments do not contribute to the set of associated namespaces.—end note ]
Note the emphasis on may.
With the above points and a couple of others from 3.4 (name lookup), one could believe that Clang and MSVC are using these rules to find d[] first (and thus finding it as C::operator[]) vs. using 13.5.5 to turn d[] into d.operator[] and continuing compilation.
It should be noted that bringing the operators of the base classes into scope of the D class or using explicit scope does, however, 'fix' this issue across all three compilers (as is expected based on the using declaration clauses in the references), example:
struct X{};
struct Y{};
template<typename T>
struct B
{
void f(X) { }
void operator[](X) {}
};
template<typename T>
struct C
{
void f(Y) { }
void operator[](Y) {}
};
template<typename T>
struct D : B<T>, C<T>
{
using B<T>::operator[];
using C<T>::operator[];
};
int main()
{
D<float> d;
d.B<float>::operator[](X()); // OK
//d.B<float>::operator[](Y()); // Error
//d.C<float>::operator[](X()); // Error
d.C<float>::operator[](Y()); // OK
d[Y()]; // calls C<T>::operator[](Y)
return 0;
}
Since the standard is ultimately left to the interpretation of the implementer, I'm not sure which compiler would be technically correct in this instance since MSVC and Clang might be using other rules to compile this though, given the subscripting paragraphs from the standard, I'm inclined to say they are not strictly adhering to the standard as much as GCC is in this instance.
I hope this can add some insight into the problem.
I believe that Clang and MSVC are incorrect, and GCC is correct to reject this code. This is an example of the principle that names in different scopes do not overload with each other. I submitted this to Clang as llvm bug 26850, we'll see if they agree.
There is nothing special about operator[] vs f(). From [over.sub]:
operator[] shall be a non-static member function with exactly one parameter. [...] Thus, a subscripting expression x[y] is interpreted as x.operator[](y) for a class object x of type T
if T::operator[](T1) exists and if the operator is selected as the best match function by the overload
resolution mechanism
So the rules governing the lookup of d[Y()] are the same as the rules governing d.f(X()). All the compilers were correct to reject the latter, and should have also rejected the former. Moreover, both Clang and MSVC reject
d.operator[](Y());
where both they accept:
d[Y()];
despite the two having identical meaning. There is no non-member operator[], and this is not a function call so there is no argument-dependent lookup either.
What follows is an explanation of why the call should be viewed as ambiguous, despite one of the two inherited member functions seeming like it's a better match.
The rules for member name lookup are defined in [class.member.lookup]. This is already a little difficult to parse, plus it refers to C as the object we're looking up in (which in OP is named D, whereas C is a subobject). We have this notion of lookup set:
The lookup set for f in C, called S(f,C), consists of two component sets: the declaration set, a set of
members named f; and the subobject set, a set of subobjects where declarations of these members (possibly
including using-declarations) were found. In the declaration set, using-declarations are replaced by the set
of designated members that are not hidden or overridden by members of the derived class (7.3.3), and type
declarations (including injected-class-names) are replaced by the types they designate.
The declaration set for operator[] in D<float> is empty: there is neither an explicit declaration nor a using-declaration.
Otherwise (i.e., C does not contain a declaration of f or the resulting declaration set is empty), S(f,C) is
initially empty. If C has base classes, calculate the lookup set for f in each direct base class subobject Bi,
and merge each such lookup set S(f,Bi) in turn into S(f,C).
So we look into B<float> and C<float>.
The following steps define the result of merging lookup set S(f,Bi)
into the intermediate S(f,C):
— If each of the subobject members of S(f,Bi) is a base class subobject of at least one of the subobject
members of S(f,C), or if S(f,Bi) is empty, S(f,C) is unchanged and the merge is complete. Conversely,
if each of the subobject members of S(f,C) is a base class subobject of at least one of the
subobject members of S(f,Bi), or if S(f,C) is empty, the new S(f,C) is a copy of S(f,Bi).
— Otherwise, if the declaration sets of S(f,Bi) and S(f,C) differ, the merge is ambiguous: the new
S(f,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.
— Otherwise, the new S(f,C) is a lookup set with the shared set of declarations and the union of the
subobject sets.
The result of name lookup for f in C is the declaration set of S(f,C). If it is an invalid set, the program is
ill-formed. [ Example:
struct A { int x; }; // S(x,A) = { { A::x }, { A } }
struct B { float x; }; // S(x,B) = { { B::x }, { B } }
struct C: public A, public B { }; // S(x,C) = { invalid, { A in C, B in C } }
struct D: public virtual C { }; // S(x,D) = S(x,C)
struct E: public virtual C { char x; }; // S(x,E) = { { E::x }, { E } }
struct F: public D, public E { }; // S(x,F) = S(x,E)
int main() {
F f;
f.x = 0; // OK, lookup finds E::x
}
S(x, F) is unambiguous because the A and B base subobjects of D are also base subobjects of E, so S(x,D)
is discarded in the first merge step. —end example ]
So here's what happens. First, we try to merge the empty declaration set of operator[] in D<float> with that of B<float>. This gives us the set {operator[](X)}.
Next, we merge that with the declaration set of operator[] in C<float>. This latter declaration set is {operator[](Y)}. These merge sets differ, so the merge is ambiguous. Note that overload resolution is not considered here. We are simply looking up the name.
The fix, by the way, is to add using-declarations to D<T> such that there is no merge step done:
template<typename T> struct D : B<T>, C<T> {
using B<T>::operator[];
using C<T>::operator[];
};

Using "using" twice interpreted differently by different compilers

Consider the following code:
struct A {
int propose();
};
struct A1 : A {
int propose(int);
using A::propose;
};
struct B1 : A1 {
protected:
using A1::propose;
public:
using A::propose;
};
int main() {
B1().propose();
}
Let's compile this: g++ -std=c++11 main.cpp.
I'm getting the following compiler error using GNU 4.8.1:
main.cpp: In function 'int main()':
main.cpp:2:9: error: 'int A::propose()' is inaccessible
int propose();
^
main.cpp:18:18: error: within this context
B1().propose();
However, this code compiles in AppleClang 6.0.0.6000056.
I understand that there is no need for the using in B1, (in my code was necessary, but I had 1 using too much by mistake). In any case, why Clang compiles it? Is this expected?
In [namespace.udecl], we have:
When a using-declaration brings names from a base class into a derived class scope, member functions and
member function templates in the derived class override and/or hide member functions and member function
templates with the same name, parameter-type-list (8.3.5), cv-qualification, and ref-qualifier (if any) in a
base class (rather than conflicting).
The standard explicitly says that names brought in will not conflict with names in a base class. But it doesn't say anything about bringing in conflicting names.
The section also says:
A using-declaration is a declaration and can therefore be used repeatedly where (and only where) multiple
declarations are allowed. [ Example:
struct B {
int i;
};
struct X : B {
using B::i;
using B::i; // error: double member declaration
};
—end example ]
And interestingly, in the following example it's GCC that happily compiles it (and prints A) while Clang allows the construction of a C but rejects the call to foo as ambiguous:
struct A {
void foo() { std::cout << "A\n"; }
};
struct B {
void foo() { std::cout << "B\n"; }
};
struct C : A, B {
using A::foo;
using B::foo;
};
int main()
{
C{}.foo();
return 0;
}
So the short answer is - I suspect this is underspecified in the standard and that both compilers are doing acceptable things. I would just avoid writing this sort of code for general sanity.
The declaration is legal.
Calling it is legal and should work anywhere, and it can only be called from the class and derived classes, and it can be called from within any class. You'll note that this makes little sense.
There are no rules that ban that construct in declarations (importing the name twice from two different base classes with the same signature), and it is even used in "real" code where the derived class goes and hides the name after they are imported.
If you don't hide it, you are in the strange situation where the same function A::propose is both protected and public at the same time, as it is named twice (legally) in the same scope with different access control. This is ... unusual.
If you are within a class, a sub-clause says you can use it:
[class.access.base]/5.1
A member m is accessible at the point R when named in class N if — (5.1) m as a member of N is public
and propose is clearly public. (it is also protected but we don't have to keep reading for that case!)
Elsewhere, we have a contradiction. You are told you can use it everywhere without restriction [class.access]/1(3). And you are told that you can only use it in certain circumstances [class.access]/1(2).
I am uncertain how to resolve that ambiguity.
The rest of the logic train:
In [namespace.udecl]/10 we have:
A using-declaration is a declaration and can therefore be used repeatedly where (and only where) multiple declarations are allowed.
And [namespace.udecl]/13:
Since a using-declaration is a declaration, the restrictions on declarations of the same name in the same declarative region
so each of those using X::propose; are declarations.
[basic.scope] has no applicable restrictions on two functions of the same name in a scope, other than [basic.scope.class]/1(3) which states that if reordering of declarations changes the program, the program is ill-formed. So we cannot say that the later one wins.
Two declarations of member functions in the same scope are legal under [basic.scope]. However, under [over], there are restrictions on two member functions with the same name.
[over]/1 states:
When two or more different declarations are specified for a single name in the same scope, that name is said to be overloaded
And there are some restrictions on overloading. This is what usually prevents
struct foo {
int method();
int method();
};
from being legal. However:
[over.load]/1 states:
Not all function declarations can be overloaded. Those that cannot be overloaded are specified here. A program is ill-formed if it contains two such non-overloadable declarations in the same scope. [Note: This
restriction applies to explicit declarations in a scope, and between such declarations and declarations made through a using-declaration (7.3.3). It does not apply to sets of functions fabricated as a result of name lookup (e.g., because of using-directives) or overload resolution (e.g., for operator functions). —end note
the note explicitly permits symbols introduced via two using-declarations from being considered by the overloading restrictions! The rules only apply to two explicit declarations within the scope, or between an explicit declaration within the scope and a using declaration.
There are zero restrictions on two using-declarations. They can have the same name, and their signatures can conflict as much as you'd like.
This is useful, because usually you can go and then hide their declaration (with a declaration in the derived class), and nothing goes wrong [namespace.udecl]/15:
When a using-declaration brings names from a base class into a derived class scope, member functions and member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list (8.3.5), cv-qualification, and ref-qualifier (if any) in a base class (rather than conflicting).
However, this is not done here. We then call the method. Overload resolution occurs.
See [namespace.udecl]/16:
For the purpose of overload resolution, the functions which are introduced by a
using-declaration into a derived class will be treated as though they were members of the derived class. In particular, the implicit this parameter shall be treated as if it were a pointer to the derived class rather than to the base class. This has no effect on the type of the function, and in all other respects the function remains a member of
the base class.
So we have to treat them as if they are members of the derived class for the purpose of overload resolution. But there are still 3 declarations here:
protected:
int A::propose(); // X
int A1::propose(int); // Y
public:
int A::propose(); // Z
Thus the call to B1().propose() considers all 3 declarations. Both X and Z are equal. They, however, refer to the same function, and overload resolution states there is an ambiguity if two different functions are selected. So the result is not ambiguous. There may be access control violations, or not, depending on how you read the standard.
[over.match]/3
If a best viable function exists and is unique, overload resolution succeeds and produces it as the result. Otherwise overload resolution fails and the invocation is ill-formed. When overload resolution succeeds, and the best viable function is not accessible (Clause 11) in the context in which it is used, the program is ill-formed.

what is the expected behavior?

Below is a purely academically invented class hierarchy.
struct X{
void f1();
void f2();
void f3();
};
struct Y : private X{
void f4();
};
struct Z : X{
};
struct D : Y, Z{
using X::f2;
using Z::X::f3;
};
int main(){}
I expected using declaration for X::f2 to be ambiguous as 'X' is an ambiguous base of 'D' (visbility vs accessibility of X). However g++ (ideone.com) compiles it fine.
I checked with Online Comeau and it gives error in using declaration for X::f2 as expected. However it gives ambiguity for using declaration for Z::X::f3 as well.
So what is the expected behavior?
Edit 1:
A reference to the appropriate section of the Standard would be helpful, please.
Edit 2:
I checked with VS 2010 and it has objections only with the using declaration X::f2. However it is not about ambiguity of 'X' (as in the case of gcc and Comeau). It is about "error C2876: 'X' : not all overloads are accessible".
Edit 3:
struct X{
void f(){}
};
struct Y : X{
struct trouble{
void f(){}
};
};
struct trouble : X{
};
struct letscheck : Y, trouble{
using trouble::f;
};
int main(){}
Here I have attempted (purposefully) to create an issue with types in using declaration. Gcc still compiles this fine and so does VS2010. Comeau still gives error (as expected) about ambiguous types 'trouble'. Going by explanations given for the initial queries, it appears GCC and VS2010 are wrong. Is that correct?
I don't think that any of these are ill-formed. First, for using X::f2, X is looked up, and this will unambiguously yield the class type X. Then f2 in X is looked up, and this is unambiguous too (it is not looked up in D!).
The second case will work for the same reason.
But if you call f2 on a D object, the call will be be ambiguous because the name f2 is looked up in all subobjects of D of type X, and D has two such subobjects, and f2 is a non-static member function. The same reason holds for the second case. It does not make a difference for this whether you name f3 using Z::X or X directly. Both of these designate the class X.
To get an ambiguity for the using declaration, you need to write it differently. Note that in C++0x using ThisClass::...; is not valid. It is in C++03 though, as long as the whole name refers to a base-class member.
Conversely, if this would be allowed in C++0x, the whole using declaration would also be valid, because C++0x does not take subobjects into account for name-lookup: D::f2 unambiguously refers to only one declaration (the one in X). See DR #39 and the final paper N1626.
struct D : Y, Z{
// ambiguous: f2 is declared in X, and X is a an ambiguous base class
using D::f2;
// still fine (if not referred to by calls/etc) :)
using Z::X::f3;
};
struct E : D {
// ambiguous in C++03
// fine in C++0x (if not referred to by an object-context (such as a call)).
using D::f2;
};
The C++03 Standard describes this in paragraphs 10.2 and 3.4.3.1.
Response for Edit3:
Yes, GCC and VS2010 are wrong. trouble refers to the type found by the injected class name of ::trouble and to the nested class found as Y::trouble. The name trouble preceeding the :: is looked up using unqualified lookup (by 3.4.1/7, which delegates to 10.2 in the first bullet) ignoring any object, function and enumerator names (3.4.3/1 - there are no such names in this case, though). It then violates against 10.2's requirement that:
If the resulting set of declarations are not all from sub-objects of the same type ... the program is ill-formed.
It is possible that VS2010 and GCC interpret C++0x wording differently than Comeau and retroactively implement that wording:
In a using-declaration used as a member-declaration, the nested-name-specifier shall name a base class of the class being defined.
This means that non-base classes are considered, but it is an error if a non-base class is named. If the Standard would intend to ignore non-base class names, it would say can only here, or spell it out explicitly (both practices are done). The Standard however is not at all consequent with its use of shall and can. And GCC implements C++0x wording, because it rejects otherwise completely fine C++03 code, just because the using declaration contains its class-name.
For an example of the unclear wording, consider the following expression:
a.~A();
This is syntactically ambiguous, because it can be a member function call if a is a class object, but it can be a pseudo-destructor-call (which is a no-op) if a has a scalar type (such as int). But what the Standard says is for the syntax of a pseudo-destructor call and class member access at 5.2.4 and 5.2.5 respectively
The left-hand side of the dot operator shall be of scalar type.
For the first option (dot) the type of the first expression (the object expression ) shall be “class object” (of a complete type).
That is the wrong use, because it does not clear up the ambiguity at all. It should use "can only", and compilers interpret it in that way. This has mostly historical reasons, as some committee-member recently told me on usenet. See The rules for the structure and drafting of International Standards, Annex H.
using X::f2; should not work due to private inheritance of below code
struct Y : private X{
void f4();
};
It is not possible to access members of X through Y.
So X::f2 would conflicts.
Z::X::f2 should work.
Or Z::f2 should work.
First, to clarify Johannes' answer. When you say using Z::X::f2;, the compiler does not "build a path" to f2 to keep track of how it should be accessed. Since Z::X is the same thing as X, the declaration is exactly the same as saying using X::f2;. Contrast it with this example:
struct A { void f() {} void g() {} };
struct B { void f() {} void g() {} };
struct C { typedef A X; };
struct D { typedef B X; };
struct E : A, B {
using C::X::f; // C::X == A
using D::X::g; // D::X == B
};
The syntax Z::X works not because of inheritance or membership, but because the identifier X is accessible from the scope Z. You are even allowed to write Z::Z::Z::Z::X::X::X::X ad nauseam, because every class brings its own name into its own scope. Thus :: here does not express inheritance.
Now, to solve the problem. f2 is inherited by Y and Z from X. Thus, it is a first-class member of Y and Z. E doesn't need to know about X because it is a hidden implementation detail. So, you want
struct D : Y, Z{
using Y::f2; // error: inaccessible
using Z::f3;
};
To explain in terms of 9.1/2 as you ask:
A class-name is inserted into the
scope in which it is declared
immediately after the class-name is
seen. The class-name is also inserted
into the scope of the class itself;
this is known as the
injected-class-name.
The name X is injected into X as X::X. It is then inherited into Y and Z. Y and Z do not implicitly declare X in their own scope.
10.2/2:
The following steps define the result of name lookup in a class
scope, C. First, every declaration for
the name in the class and in each of
its base class sub-objects is
considered. …
If the resulting set of declarations
are not all from sub-objects of
the same type, or the set has a
nonstatic member and includes members
from distinct sub-objects, there is an
ambiguity and the program is
ill-formed. Otherwise that set is the
result of the lookup.
Note that I bolded the plural word sub-objects. Although the name X is found in two sub-objects, they are both the same type, namely X.