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

3

6

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.

Agulhas answered 9/11, 2014 at 19:16 Comment(11)
Can you add the full compiler error message?Uralite
Ohh I miss understood, yee compiler error plz.Hyperactive
Maybe this is useful: #6489272Mhd
@rupesh.yadav: there is no error until you try to use these members directly, eg D::method() { a = A(); } or D d = D(); d.a = A();.Mhd
Excellent question. But be carefull: you have here multiple inheritance without virtual base, this means that D has three A bases and you copy only one of them. Make a trace, logging for each class the copy constructor and the default constructor and you'll see that several A default constructors will be triggered.Fulfil
Why do you need C to inherit from both A and B given that B already inherits from A?Leif
I don't. I suppose I would never write a code like this. It's an excercise given to me and fellow students by the lecturer. It's possible that this code snippet is not what I was meant to be by some mistake but I wanted to discuss it here before coming to such conclusion.Agulhas
There is no way to do it. That's what the warning is telling you. See Johannes's answer for an explanation of what's uniquely inaccessible about the direct base.Bearish
@BenVoigt There is a way to do it, please see my answer (including code). The duplicate you are refering to is different from this case: it's related to multiple inheritance as well, but the common base is private in one one inheritance path and public on the other. The way the visibility/amiguity is handled there will not work here.Fulfil
@Christophe: Your answer is wrong. D::A refers to a type, not a subobject. And the problem never was naming the base class, it's the argument. When you pass _d to A (or D::A), it needs to be implicitly converted to the argument type, const A&. But the implicit conversion fails due to ambiguity. There is no way to name the A subobject of a D which was introduced at D (rather than part of the C base subobject)Bearish
Sorry Ben but it compiled with the smeantic that I expectected. D::A refers to a type, which happens to to be a class of D which makes this a valid syntax according to C++11 sect.12.6.2. For member disambiguation, see sect.10.1/5Fulfil
F
5

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.

Fulfil answered 9/11, 2014 at 20:37 Comment(4)
I already tried that, it seemed reasonable to me somehow but unfortunately it does not compile neither on MinGW 4.8.1 nor on Microsoft Visual C++ 2013. Both compilers produce similar error messages: that 'A' is an ambiguous base of 'D' and that class 'A' has already been initializedAgulhas
The proposed solution doesn't compile. It's ambiguous which A subobject of _d should be used to initialize the base class.Pagan
I compiled all the code above in MSVC 2013. But attention: the intellisense highlighted an error for ambiguity, wich is not reported by code generation. And compiled code worked perfectly (as it should, because it's valid construct).Fulfil
@Agulhas well, following your remark I spent the night browsing through the standard, to find out that my solution worked only because of a permissive compiler (bug?). Now I have another solution which will work on all compilers.Fulfil
N
0

The copy constructor generated by the compiler would call the default constructor of grand-father base class. This is probably not what you want. To have a clean design with only copy constructors called, you should implement the copy constructor of each class and make sure they are called as you need. In your current hierarchy example, you cannot cast directly 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 edit:

But to begin with, and to avoid a lot of the ambiguity problems in your exercice, you should have used 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 the 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). So you declare also 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 there is only one shared instance of A in your hierarchy. Then, you can write the D copy constructor as you wanted:

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

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

Northerner answered 9/11, 2014 at 19:40 Comment(7)
Yes, it compiles. But we have here multiple inheritance without virtual bases. In this copy constructor you only define how to copy the A base from which B inherits. It doesn't copy the A base from wich D inherits directly, nor the A base from which C inherits ! These two get initalized with their default constructor !!!Fulfil
That's not equivalent to the default copy constructor at all.Pagan
Yes, my first answer was not accurate. I have completed it.Northerner
Would it be possible that you test your proposed solution with the verification code I propose in my answer startic at sencence "If you add a member int v;" ? I've tried your solution, and it seems that one of the base class A (direct base of D) gets copied from a wrong base class A (the one which is base for B). Could you check ?Fulfil
I am using clang++ version 3.4 in C++11 mode, and it prevents me from casting directly a D object into an A object as you propose. The error reported is: ambiguous conversion from derived class 'D' to base class 'A'.Northerner
Editing my answer to include virtual inheritance, I think it must be introduced to prevent ambiguous conversion in the hierarchy.Northerner
@oikosdev +1 for your edit and the virtual base. I reviewed my answer as well, having reread standard despite my too permissive compiler.Fulfil
A
0

Please pass by reference the dual parameter.

Forward passes are ok with dual parameter passed as reference.


// The time series function which is t ^ 5
auto func(dual *t) {
    return autodiff::forward::pow(*t, 5.0);
}
// gradient calculation which is at (t)
var func_gradient(dual *t) {
    auto u = func(t);  // the output variable u
    auto ux = autodiff::forward::derivative(u, autodiff::forward::wrt(t), at(t)); // evaluate the derivative of u with respect to x
    return (var) ux;
}

Arleyne answered 25/3, 2020 at 15:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.