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;
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
gd1
  • 11,300
  • 7
  • 49
  • 88
  • You need a::f as virtual method to override it – progsource May 04 '15 at 17:34
  • 4
    I am not overriding anything. I am hiding. – gd1 May 04 '15 at 17:34
  • [Works here](http://coliru.stacked-crooked.com/a/3e301b60717fd4dc). What compiler are you using? – David G May 04 '15 at 17:36
  • 1
    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) –  May 04 '15 at 17:37
  • 2
    @0x499602D2: Remove the `C::f` definition. – Jarod42 May 04 '15 at 17:38
  • 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. – vsoftco May 04 '15 at 17:47
  • 6
    Re the title, you were the unfortunate victim of a rule that is a net benefit in a _huge_ majority of cases. – Lightness Races in Orbit May 04 '15 at 23:15
  • @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. :) – gd1 May 05 '15 at 07:38
  • Meta discussion: [The Halting Issue](http://meta.stackexchange.com/a/115250/7586), and [The title word filter is one of the worst ideas ever implemented on SO](http://meta.stackexchange.com/q/112944/7586) – Kobi May 05 '15 at 08:00
  • @Kobi: bottom line, it's in[s]ane. :) – gd1 May 05 '15 at 08:08
  • More relevant is http://meta.stackexchange.com/questions/108815/let-users-with-sufficient-reputation-use-problem-in-titles#comment300489_111000 – Lightness Races in Orbit May 05 '15 at 09:43

3 Answers3

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

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
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 ]

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • 6
    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? – gd1 May 04 '15 at 17:53
  • @T.C. The normative reference seems to be [expr.ref]/5. – Brian Bi May 04 '15 at 17:58
  • @gd1 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. – Barry May 04 '15 at 18:01
  • 1
    @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. – T.C. May 04 '15 at 18:05
  • @gd1 I was going to, but then Brian posted his answer, and I don't have much to add to it. – T.C. May 04 '15 at 18:05
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.

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • How does this relate to Brian's answer? – gd1 May 04 '15 at 18:00
  • 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. – Christophe May 04 '15 at 18:08
  • 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 ;) – gd1 May 04 '15 at 18:10
  • 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. – Christophe May 04 '15 at 18:24