Ambiguous call to copy constructor in C ++ caused by multiple inheritance

I have a problem with a specific task, this is an exercise, not a real program. The challenge is to define a copy constructor of structure D, which behaves exactly like the copy constructor generated by the compiler.

class Ob{ }; struct A { Ob a; }; struct B : A { Ob b; }; struct C : A, B { Ob c; }; struct D : C, A { Ob d; }; 

As you can see, structure A is indirectly derived several times in structure D, which causes ambiguity in the definition of the copy constructor as follows:

 D(const D& _d) : C(_d), A(_d), d(_d.d) {} 

My question is how to determine the copy constructor correctly? The code without the definition mentioned above is compiled, so it seems that this should be possible.

MinGW 4.8.1 error message:

 zad3.cpp:12:8: warning: direct base 'A' inaccessible in 'C' due to ambiguity [enabled by default] struct C : A, B { ^ zad3.cpp:16:8: warning: direct base 'A' inaccessible in 'D' due to ambiguity [enabled by default] struct D : C, A { ^ zad3.cpp: In copy constructor 'D::D(const D&)': zad3.cpp:17:38: error: 'A' is an ambiguous base of 'D' D(const D& _d) : C(_d), A(_d), d(_d.d) {} ^ 

IMPORTANT NOTE: This is NOT the DUPLICATE of the "Invalid Direct Base Caused by Multiple Inheritance" question as it relates to common base classes with different access specifiers and was ultimately caused by a conversion problem. Here he inherited the ambiguity of the required initializer for a common base several times with the same visibility.

+6
source share
2 answers

ATTENTION: THE RESPONSE IS FUNDAMENTALLY PICTURED!

Problem Analysis:

You are using multiple inheritance with a diamond problem .

More specifically, your D structure inherits the same base class A three times: once directly ( struct D: C,A ) and twice indirectly (via C inheritance). Since the base classes are not virtual, there are 3 different sub-objects A. For the standard section D. C ++ 11 10.1 / 4-5 is a lattice :

inheritance diagram

Typically, you would have to disambiguate the members of each A with an explicit qualification telling the compiler which of the 3 A subobjects you reference. This is explained in C ++ 11, section 10.1 / 5. The syntax for members must be A::a , C::a and B::a within D , each of which ultimately precedes D:: if you are outside.

Unfortunately, the element name lookup logic in C ++ 11, section 10.2 / 5-6, ensures that direct base A will make other indirect bases A ambiguous, despite explicit qualifications (or even using statements).

Final decision:

Since the problem is caused by the direct base class and the fact that there is no way to ambiguously assign this to others, the only really working solution is to use an empty intermediate class to create another name:

 struct Ob{ int v; }; // v aded here to allow verification of copy of all members struct A { Ob a; }; struct B : A { Ob b; }; struct A1 : A {}; // intermediary class just for diambiguation of A in C struct C : A1, B { Ob c; }; // use A1 instead of A struct A2 : A { }; // intermediary class just for diambiguation of A in D struct D : C, A2 { // use A2 instead of A Ob d; D() { } D(const D& _d) : C(_d), A2(_d), d(_d.d) { } }; int main(int ac, char**av) { cout << "Multiple inheritance\n"; D x; x.A2::av = 1; // without A2:: it ambiguous x.A1::av = 2; // without A1:: it ambiguous xB::av = 3; xbv = 4; xdv = 5; D y = x; cout << "The moment of truth: if not 1 2 3 4 5, there a problem!\n"; cout << y.A2::av << endl; cout << y.A1::av << endl; cout << yB::av << endl; cout << ybv << endl; cout << ydv << endl; } 

This code compiles and works with MSVC2013, clang 3.4.1 and gcc 4.9.


Other (non) solutions:

My previous answer was based only on explicit qualifications. Despite many criticisms, I really compiled and tested on MSVC2013! However, they were strange: the ambiguity was emphasized in the intelisence editor, but the compilation went fine without errors. At first I thought it was an error with intelligence, but now I realized that this is not a compiler match (an error?)

The answer suggesting D(const D& other) : C(other), A((const B)other), d(other.d) compiles, but fails the test. What for? because A((const B)other) will understand other as B Thus, A directly in D will receive initialization with the value of A indirectly inherited from B (so on A ). This is a very unpleasant mistake, and it took me a while to notice.

Of course, you can use virtual base classes. Then in D there will be only one subobject A , which solves many problems. However, I am not constructing what you are designing, and some designs require a grid, not a virtualized diamond.

If you can afford a two-step copy (step 1: initializing the default database, step 2: copying the target value on the base), there are, of course, approaches that use two-digit member functions that return a link to the correct base. But it can be more complex and error prone than the simple solution presented above.

+5
source

The copy constructor generated by the compiler will invoke the default constructor of the grand-father base class. This is probably not what you want. In order to have a clean design with only copy constructors called, you must implement the copy constructor of each class and make sure they are called as needed. In the example with your current hierarchy, you cannot directly translate from D to A.

 A(const A& other), a(other.a) { } //... B(const B& other) : A(other), b(other.b) { } //... C(const C& other) : B(other), A((const B)other), c(other.c) { } //... D(const D& other) : C(other), A((const B)other), d(other.d) { } 

Further editing:

But first, and to avoid many of the problems of ambiguity in your practice, you should use virtual inheritance .

In your case, there is an ambiguous path from C to A: either C-> A, or C-> B-> A To declare A as a common ancestor in the hierarchy, you declare inheritance from B or C to A as virtual. There is also an ambiguous path from D to A: either D-> A, or (D-> C-> A, or D-> C-> B-> A). Thus, you also declare the inheritance from D to A as virtual.

 struct B : public virtual A { ... } struct C : public virtual A, public B { ... } struct D : public C, public virtual A { ... } 

Now your hierarchy has only one common instance of A. Then you can write the D-copy constructor as you would like:

 D(const D& other) : C(other), A(other), d(other.d) 

The element D :: A :: a will be the same as D :: C :: A :: a or D :: C :: B :: A :: a.

0
source

Source: https://habr.com/ru/post/977951/


All Articles