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

3

53

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;
}
Oren answered 4/5, 2015 at 17:31 Comment(11)
You need a::f as virtual method to override itCovin
I am not overriding anything. I am hiding.Oren
Works here. What compiler are you using?Fonsie
Probably: There is no B1::f (the name is resolved as A::f), hence you have two A::f (one in B1 and one in B2)Deste
@0x499602D2: Remove the C::f definition.Guizot
It probably has to do with how name lookup is performed. It seems that the using directive is added to the overload set after the unqualified name lookup. In particular, if you uncomment the using directive but remove the explicit call c.f(), your program compiles.Psaltery
Re the title, you were the unfortunate victim of a rule that is a net benefit in a huge majority of cases.Americium
@LightnessRacesinOrbit : I +1'd your comment because you have shown empathy towards my suffering, anyway I beg to differ: such a rule is frankly in[s]ane because it is useless when it's not harmful. The bad guys can simply write "issue" in the title box, or "proble.m", and then write "plz help how declare pointer cheerz" in the body. There's no automagic word filter saving you, you'll have to hunt low-quality questions down one by one. And let me write "diamond problem", for God's sake. :)Oren
Meta discussion: The Halting Issue, and The title word filter is one of the worst ideas ever implemented on SODull
@Kobi: bottom line, it's in[s]ane. :)Oren
More relevant is meta.stackexchange.com/questions/108815/…Americium
E
57

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

Eric answered 4/5, 2015 at 17:53 Comment(1)
So, the using-declare is not smart enough, otherwise should access the ones in A inherited from B1.Cittern
G
29

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 ]

Gobi answered 4/5, 2015 at 17:48 Comment(5)
Great, thanks. I am willing to accept this answer, but can you explain me in your words what they mean with "designates a base class member (and not a member subobject or a member function of a base class subobject)". Ehm... what?Oren
@Gobi The normative reference seems to be [expr.ref]/5.Eric
@Oren The using-declaration designates a member (A::x()) and not a subobject (either the C or the B that contain the A that has the x()). You need to specify the subobject to resolve the ambiguity.Skink
@Brian Sure, that's what ultimately renders the call ill-formed, but to be complete you'd need to go through a lot more than that. Since the question didn't ask for a full language-lawyer analysis, I figured that the note is good enough.Gobi
@Oren I was going to, but then Brian posted his answer, and I don't have much to add to it.Gobi
P
5

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.

Proudhon answered 4/5, 2015 at 17:54 Comment(4)
How does this relate to Brian's answer?Oren
Brian's very clear answer was posted in the same time. I wouldn't have posted my answer if I'd read his one first ;-) But I don't delete mine, just to leave the standard reference for the language lawyers who might be interested.Proudhon
I don't think your answer is useless, I am just trying to understand how it relates to Brian's. I don't see how 10.2/3 would explain what I see, whereas the note reported by T.C. and Brian's explanation of it make sense to me--when used together ;)Oren
I've completed the quote with the first sentence. My explanation is solely focused on the declarations set (and the fact that tehre is ambiguity left). Brian addresses as well the subobject set, and the special case of your example.Proudhon

© 2022 - 2024 — McMap. All rights reserved.