Why doesn't a using-declaration work to solve the diamond problem?

C++InheritanceSubclassUsing DirectivesDiamond Problem

C++ Problem Overview


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;
}

C++ Solutions


Solution 1 - C++

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?)

Solution 2 - C++

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 ]

Solution 3 - C++

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questiongd1View Question on Stackoverflow
Solution 1 - C++Brian BiView Answer on Stackoverflow
Solution 2 - C++T.C.View Answer on Stackoverflow
Solution 3 - C++ChristopheView Answer on Stackoverflow