Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ambiguous call of a copy constructor in C++ caused by multiple inheritance

I'm having a problem with a certain task, it's an excercise, not a real program. The task is to define a copy constructor of structure D that behaves in the exactly same way as a copy constructor generated by the compliler would.

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 few times in the D structure what causes ambiguity in a definition of a copy constructor like this:

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

My question is how to define that copy constructor correctly? The code without the definition mentioned above compiles, so it seems that it 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 REMARK: This is NOT A DUPLICATE of the the question "Inaccessible direct base caused by multiple inheritance", which was about common base classes with different access specifiers and was in the end caused by a conversion issue. Here it's about disambiguating a required initializer for a common base inherited several times with the same visibility.

like image 445
borrisso Avatar asked Nov 09 '14 19:11

borrisso


People also ask

What is ambiguity in multiple inheritance explain with example?

Inheritance Ambiguity in C++ In multiple inheritances, when one class is derived from two or more base classes then there may be a possibility that the base classes have functions with the same name, and the derived class may not have functions with that name as those of its base classes.

Can copy constructor be inherited?

Copy constructor is not inherited.

Can you have multiple copy constructors?

A class can have multiple copy constructors, e.g. both T::T(const T&) and T::T(T&). If some user-defined copy constructors are present, the user may still force the generation of the implicitly declared copy constructor with the keyword default .

What is multiple inheritance example?

Multiple Inheritance is a feature of C++ where a class can inherit from more than one classes. The constructors of inherited classes are called in the same order in which they are inherited. For example, in the following program, B's constructor is called before A's constructor.


1 Answers

ATTENTION: ANSWER FUNDAMENTALLY EDITED !

Analysis of the problem:

You are using multiple inheritance with a diamond problem.

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

inheritance diagram

Normally, you would then disambiguate members of each A with explicit qualification telling the compiler which of the 3 A subobject you are refering to. This is explained in C++11 section 10.1/5. The syntax for members should be A::a, C::a and B::a within the scope of D, each eventually preceded by D:: if you are outside.

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

Definitive solution:

As the problem is caused by the direct base class, and the fact that there are no ways to diambiguate this one from the others, the only really working solution is to use an empty intermediary class to force a different 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::a.v = 1;  // without A2:: it's ambiguous
    x.A1::a.v = 2;  // without A1:: it's ambiguous
    x.B::a.v = 3;
    x.b.v = 4;
    x.d.v = 5;

    D y = x;
    cout << "The moment of truth: if not 1 2 3 4 5, there's a problem!\n";
    cout << y.A2::a.v << endl;
    cout << y.A1::a.v << endl;
    cout << y.B::a.v << endl;
    cout << y.b.v << endl;
    cout << y.d.v << endl;
}

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


Other (non-)solutions:

My previous answer was based only on explicit qualification. Despite many criticism, I really got it compiled and tested successfully on MSVC2013 ! However their was a weird thing: in the editor intelisence highlighted an ambiguity but the compilation ran fine without any error. I first thought that this was an intelisence bug, but now realize that this was a compiler non compliance (bug ?)

The answer suggesting D(const D& other) : C(other), A((const B)other), d(other.d) compiles, but does not pass the test. Why ? because A((const B)other) will understand other as being a B. So the A directly in D would get initialized with the value of the A indirectly inherited from B (so another A). This is an extremely nasty error and it took me a while to notice.

Of course you could use virtual base classes. Then there will be only a single A subobject in D, which solves many problems. However I don't konw what you are designing, and some designs require a lattice rather than a virtualized diamond.

If you could afford a two steps copy (step 1: default initialisation of the base; step 2: copying the target value on the base), there are certainly approaches using diambiguated member functions returning the reference of the correct base. But that might be more tricky and error prone than the simple solution presented above.

like image 189
Christophe Avatar answered Sep 22 '22 11:09

Christophe