views:

235

answers:

3

Below is a purely academically invented class hierarchy.

struct X{
        void f1();
        void f2();
        void f3();
};

struct Y : private X{
        void f4();
};

struct Z : X{
};

struct D : Y, Z{
        using X::f2;
        using Z::X::f3;
};

int main(){}

I expected using declaration for X::f2 to be ambiguous as 'X' is an ambiguous base of 'D' (visbility vs accessibility of X). However g++ (ideone.com) compiles it fine.

I checked with Online Comeau and it gives error in using declaration for X::f2 as expected. However it gives ambiguity for using declaration for Z::X::f3 as well.

So what is the expected behavior?

Edit 1:

A reference to the appropriate section of the Standard would be helpful, please.

Edit 2:

I checked with VS 2010 and it has objections only with the using declaration X::f2. However it is not about ambiguity of 'X' (as in the case of gcc and Comeau). It is about "error C2876: 'X' : not all overloads are accessible".

Edit 3:

struct X{
    void f(){}
};

struct Y : X{
    struct trouble{
        void f(){}
    };

};

struct trouble : X{
};

struct letscheck : Y, trouble{
    using trouble::f;
};

int main(){}

Here I have attempted (purposefully) to create an issue with types in using declaration. Gcc still compiles this fine and so does VS2010. Comeau still gives error (as expected) about ambiguous types 'trouble'. Going by explanations given for the initial queries, it appears GCC and VS2010 are wrong. Is that correct?

A: 

using X::f2; should not work due to private inheritance of below code

struct Y : private X{
    void f4();
};

It is not possible to access members of X through Y. So X::f2 would conflicts.

Z::X::f2 should work. Or Z::f2 should work.

bjskishore123
C++ has the multiple-access rule. If a name can be found by multiple paths in a base class hierarchy, and one of them is public, that public access path is taken. See `11.7/1`.
Johannes Schaub - litb
@litb: So it means that 'using X::f2' is fine as 'X' is accessible via 'Z' in OP. But then 'X' is ambiguous and hence ambiguity related error. So, VS2010 error message about 'not all overloads are accessible' is probably tricky. This means that Comeau is right. I least suspected the error about using declaration for 'Z::X::f3'.
Chubsdad
@chubsdad it just means that it is not an accessibility error. It changes the access to the path that gives the most access. It would not be an ambiguous lookup even without 11.7/1, but then we wouldn't know whether we need to apply private or public accessibility.
Johannes Schaub - litb
+2  A: 

I don't think that any of these are ill-formed. First, for using X::f2, X is looked up, and this will unambiguously yield the class type X. Then f2 in X is looked up, and this is unambiguous too (it is not looked up in D!).

The second case will work for the same reason.

But if you call f2 on a D object, the call will be be ambiguous because the name f2 is looked up in all subobjects of D of type X, and D has two such subobjects, and f2 is a non-static member function. The same reason holds for the second case. It does not make a difference for this whether you name f3 using Z::X or X directly. Both of these designate the class X.

To get an ambiguity for the using declaration, you need to write it differently. Note that in C++0x using ThisClass::...; is not valid. It is in C++03 though, as long as the whole name refers to a base-class member.

Conversely, if this would be allowed in C++0x, the whole using declaration would also be valid, because C++0x does not take subobjects into account for name-lookup: D::f2 unambiguously refers to only one declaration (the one in X). See DR #39 and the final paper N1626.

struct D : Y, Z{
    // ambiguous: f2 is declared in X, and X is a an ambiguous base class
    using D::f2;

    // still fine (if not referred to by calls/etc) :)
    using Z::X::f3;
};

struct E : D {
  // ambiguous in C++03
  // fine in C++0x (if not referred to by an object-context (such as a call)).
  using D::f2;
};

The C++03 Standard describes this in paragraphs 10.2 and 3.4.3.1.


Response for Edit3:

Yes, GCC and VS2010 are wrong. trouble refers to the type found by the injected class name of ::trouble and to the nested class found as Y::trouble. The name trouble preceeding the :: is looked up using unqualified lookup (by 3.4.1/7, which delegates to 10.2 in the first bullet) ignoring any object, function and enumerator names (3.4.3/1 - there are no such names in this case, though). It then violates against 10.2's requirement that:

If the resulting set of declarations are not all from sub-objects of the same type ... the program is ill-formed.


It is possible that VS2010 and GCC interpret C++0x wording differently than Comeau and retroactively implement that wording:

In a using-declaration used as a member-declaration, the nested-name-specifier shall name a base class of the class being defined.

This means that non-base classes are considered, but it is an error if a non-base class is named. If the Standard would intend to ignore non-base class names, it would say can only here, or spell it out explicitly (both practices are done). The Standard however is not at all consequent with its use of shall and can. And GCC implements C++0x wording, because it rejects otherwise completely fine C++03 code, just because the using declaration contains its class-name.

For an example of the unclear wording, consider the following expression:

a.~A();

This is syntactically ambiguous, because it can be a member function call if a is a class object, but it can be a pseudo-destructor-call (which is a no-op) if a has a scalar type (such as int). But what the Standard says is for the syntax of a pseudo-destructor call and class member access at 5.2.4 and 5.2.5 respectively

The left-hand side of the dot operator shall be of scalar type.

For the first option (dot) the type of the first expression (the object expression ) shall be “class object” (of a complete type).

That is the wrong use, because it does not clear up the ambiguity at all. It should use "can only", and compilers interpret it in that way. This has mostly historical reasons, as some committee-member recently told me on usenet. See The rules for the structure and drafting of International Standards, Annex H.

Johannes Schaub - litb
@litb: I modified class 'D' as follows: struct D : Y, Z{ using X::f2; using Z::X::f3; void f(){X *p = this;} }; Now I got the error prog.cpp:19: error: repeated using declaration ‘using X::f3’ prog.cpp: In member function ‘void D::f()’: prog.cpp:20: error: ‘X’ is an ambiguous base of ‘D’Are the rules different for 'using declaration' during 'name lookup'
Chubsdad
@chubsdad all i can do is to repeat the Standard. I don't know what those compilers implement. I don't see a "repeated using declaration" in your code.
Johannes Schaub - litb
@litb: slight typo in my code and hence the compiler output. The revised error follows:prog.cpp: In member function ‘void D::f()’:prog.cpp:20: error: ‘X’ is an ambiguous base of ‘D’
Chubsdad
@chubsdad i recommend you to read 10.2 in C++03 to understand what's going on. There is no conversion going on if you write a using declaration (the lookup result will simply be ambiguous). And there is no conversion going on in C++03 if you lookup `f2` later on in `D` for a call that would be ill-formed. In C++0x there is a conversion going on that's needed for the revised wording of 10.2 that is described in `5.2.5/5` in the FCD in order to diagnose the multiple-subobjects rule. In C++0x it's not an ambiguous lookup, but an ambiguous conversion, that is the error.
Johannes Schaub - litb
@chubsdad i added some fun stuff. Hope you don't mind. This should also help clearing up the matters a bit, i hope :)
Johannes Schaub - litb
@litb: Its my please to be learning from all the gurus here. And learning with 'fun' is the ultimate bliss. BTW, why did you delete that comment related to 'type and multiple subobjects'. That was the 'bull's eye' comment
Chubsdad
@chubsdad i figured the comment wasn't accurate and i would have to re-send it (replacing "object" by "non-static function or data member") but then figured it would be better to let @Potatoswatter answer :)
Johannes Schaub - litb
@litb,@potatoswatter: I also tried to have a little fun and have created Edit 3. Please check and validate my understanding from all the comments from you.
Chubsdad
@chubsdad please note that potatoswatter will not receive that message, beause he did not appear in this comment thread. Only the first nick to appear after '@' and only nicks that participate in that thread will receive notifications. I dislike that behavior, though :(
Johannes Schaub - litb
oh. Have updated @Potatoswatter's entry also
Chubsdad
@litb: am trying my best to keep pace here with you. I got lost at "This means that non-base classes are considered, but it is an error if a non-base class is named.". How does one come to that conclusion?
Chubsdad
@chubsdad see the discussion about a similar issue here: http://groups.google.com/group/comp.std.c++/browse_thread/thread/9c1662df39cbe713 . The Standard's editor Pete Becker clears the matter up nicely at the end.
Johannes Schaub - litb
A: 

First, to clarify Johannes' answer. When you say using Z::X::f2;, the compiler does not "build a path" to f2 to keep track of how it should be accessed. Since Z::X is the same thing as X, the declaration is exactly the same as saying using X::f2;. Contrast it with this example:

struct A { void f() {} void g() {} };
struct B { void f() {} void g() {} };
struct C { typedef A X; };
struct D { typedef B X; };
struct E : A, B {
    using C::X::f; // C::X == A
    using D::X::g; // D::X == B
};

The syntax Z::X works not because of inheritance or membership, but because the identifier X is accessible from the scope Z. You are even allowed to write Z::Z::Z::Z::X::X::X::X ad nauseam, because every class brings its own name into its own scope. Thus :: here does not express inheritance.

Now, to solve the problem. f2 is inherited by Y and Z from X. Thus, it is a first-class member of Y and Z. E doesn't need to know about X because it is a hidden implementation detail. So, you want

struct D : Y, Z{
    using Y::f2; // error: inaccessible
    using Z::f3;
};

To explain in terms of 9.1/2 as you ask:

A class-name is inserted into the scope in which it is declared immediately after the class-name is seen. The class-name is also inserted into the scope of the class itself; this is known as the injected-class-name.

The name X is injected into X as X::X. It is then inherited into Y and Z. Y and Z do not implicitly declare X in their own scope.

10.2/2:

The following steps define the result of name lookup in a class scope, C. First, every declaration for the name in the class and in each of its base class sub-objects is considered. … If the resulting set of declarations are not all from sub-objects of the same type, or the set has a nonstatic member and includes members from distinct sub-objects, there is an ambiguity and the program is ill-formed. Otherwise that set is the result of the lookup.

Note that I bolded the plural word sub-objects. Although the name X is found in two sub-objects, they are both the same type, namely X.

Potatoswatter
@Potatoswatter: Within the scope of 'D', there are two 'X' suboobjects (one from 'Y' (inaccessible) and one from 'Z' (accessible)). With reference to $9.2 (Injected class name). Shouldn't 'using X::f2' be ambiguous with the above logic as 'X' is an ambiguous base of 'D'?
Chubsdad
@chubs: There is only one class named `X`. `X` names a class, not a subobject: that is the point of my illustration.
Potatoswatter
@chubs: as for §9.1/2 (not $9.2), see updated answer.
Potatoswatter
@Potatoswatter: Thanks for the clarifications. Have edited the question (Edit 3) and wanted your inputs on my understanding.
Chubsdad