Please consider the following code:
struct A
{
void f()
{
}
};
struct B1 : A
{
};
struct B2 : A
{
};
struct C : B1, B2
{
void f() // works
{
B1::f();
}
//using B1::f; // does not work
//using B1::A::f; // does not work as well
};
int main()
{
C c;
c.f();
return 0;
}
I kindly ask you not to copy paste a standard reply on how to solve the diamond problem ("use virtual inheritance"). What I am asking here is why doesn't a using-declaration work in this case. The exact compiler error is:
In function 'int main()':
prog.cpp:31:6: error: 'A' is an ambiguous base of 'C'
c.f();
I got the impression a using-declaration should work from this example:
struct A
{
void f()
{
}
};
struct B
{
void f()
{
}
};
struct C : A, B
{
using A::f;
};
int main()
{
C c;
c.f(); // will call A::f
return 0;
}
Someone else can find the standard quote but I'm going to explain conceptually.
It doesn't work because a using-declaration only affects name lookup.
Your using-declaration causes name lookup to succeed where it would otherwise fail, that is, it tells the compiler where to find the function f
. But it does not tell it which A
subobject f
acts on, that is, which one will be passed as the implicit this
parameter when f
is called.
There is only a single function A::f
even though there are two A
subobjects of C
, and it takes an implicit this
argument of type A*
. In order to call it on a C
object, C*
must be implicitly converted to A*
. This is always ambiguous, and is not affected by any using-declarations.
(This makes more sense if you put data members inside A
. Then C
would have two of each such data member. When f
is called, if it accesses data members, does it access the ones in the A
subobject inherited from B1
, or the ones in the A
subobject inherited from B2
?)
There's a note in [namespace.udecl]/p17 that addresses this situation directly:
[ Note: Because a using-declaration designates a base class member (and not a member subobject or a member function of a base class subobject), a using-declaration cannot be used to resolve inherited member ambiguities. For example,
struct A { int x(); }; struct B : A { }; struct C : A { using A::x; int x(int); }; struct D : B, C { using C::x; int x(double); }; int f(D* d) { return d->x(); // ambiguous: B::x or C::x }
—end note ]
In addition to T.C.'s answer, I'd like to add that the name lookup in derived class is explained in the standard pretty much in detail in section 10.2.
Here what is said about processing of using-declarations :
10.2/3: The lookup set (...) 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 members they designate, and type declarations (including injected-class-names) are replaced by the types they designate.
So when you try to declare in struct C
using B1::f; // you hope to make clear that B1::f is to be used
according to the lookup rules, your compiler nevertheless finds the possible candidates: B1::f
and B2::f
so that it's still ambiguous.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With