Topic: Using declarations and class member ambiguities


Author: wmm@world.std.com ("William M. Miller")
Date: Fri, 11 Jul 2003 01:38:52 +0000 (UTC)
Raw View
"Daveed Vandevoorde" <google@vandevoorde.com> wrote in message
news:52f2f9cd.0307091410.785d9b5b@posting.google.com...
> whayes@hfx.andara.com ("William Hayes") wrote:
> > The Standard says that "...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" (7.3.3, 14).  The example it cites involves an
> > inheritance hierarchy containing multiple copies of a base class:
> >
> >     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
> >     }
> >
> > My first question is, Why can't the using declaration be used to perform
> > this kind of disambiguation?  If the using declaration declares function
x
> > in struct D as a "synonym" for C::x, it seems that it would be
reasonable to
> > expect that d->x() would call C::x, just as d->C::x() would.
...
> In you example, the using declarations cause the overload set (i.e.,
> the result of looking up x) to consist of:
>     A::x()
>     B::x(int)
>     C::x(double)
> Overload resolution easily picks A::x(), which means that d must
> be converted to an A*, and it is that part that is ambiguous.

Actually, the current wording of 10.2p2 makes this a lookup
ambiguity, not just a conversion ambiguity:

    Each of these declarations that was introduced by a
    using-declaration is considered to be from each sub-object of
    C that is of the type containing the declaration designated by
    the using-declaration.  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.

Since A::x() was "introduced by a using-declaration", the resulting
set is actually

    A::x() in B
    A::x() in C
    B::x(int)
    C::x(double)

Because this set "has a nonstatic member and includes members from
distinct sub-objects" (namely A::x() from B and A::x() from C), the
lookup itself is ambiguous, without resorting to the conversion
ambiguity of f to A*.

If the proposed resolution for core issue 39
(http://std.dkuug.dk/jtc1/sc22/wg21/docs/cwg_active.html#39) is
adopted, I believe that your analysis is correct -- this will no
longer be a lookup ambiguity and will be ambiguous only because of
the conversion of d to A*.  (I think this may be an unintended
consequence of the proposed resolution, and I've asked the CWG to
consider whether further work is required.)

-- William M. Miller


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: whayes@hfx.andara.com ("William Hayes")
Date: Fri, 11 Jul 2003 01:39:09 +0000 (UTC)
Raw View
""William M. Miller"" <wmm@world.std.com> wrote in message
news:bei0tn$5ev75$1@ID-166721.news.dfncis.de...
> ""William Hayes"" <whayes@hfx.andara.com> wrote in message
> news:jFXMa.33361$PD3.111394@nnrp1.uunet.ca...
> > The Standard says that "...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" (7.3.3, 14).  The example it cites involves an
> > inheritance hierarchy containing multiple copies of a base class:
> >
> >     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
> >     }
> >
> > My first question is, Why can't the using declaration be used to perform
> > this kind of disambiguation?  If the using declaration declares function
x
> > in struct D as a "synonym" for C::x, it seems that it would be
reasonable
> to
> > expect that d->x() would call C::x, just as d->C::x() would.
>
> The only effect of a using-declaration (apart from overload
> resolution) is to prevent hiding of base class names by
> derived class declarations.  It doesn't do anything else.
> In particular, the two using-declarations in the example
> above just mean that A::x() is not hidden by the declarations
> in C and D.  However, there are two subobjects of type A, so
> the lookup set has two A::x() members.  (See 10.2p2: "Each of
> these declarations that was introduced by a using-declaration
> is considered to be from each sub-object of C that is of the
> type containing the declaration designated by the
> using-declaration.")
>
> Another way of looking at this is that using-declarations
> deal only with types, not objects (cf "each sub-object ...
> of the _type_ containing the declaration..." in the preceding
> citation [emphasis added]).
>
> > In spite of the Standard's general refusal to allow the using
declaration
> to
> > resolve inherited member ambiguities, several compilers (Comeau 4.3.0.1,
> g++
> > 3.2, Borland 5.5.1) allow the following:
> >
> >     #include <cassert>
> >
> >     class X { public: char x(){ return 'X';} };
> >     class Y { public: char x(){ return 'Y';} };
> >     class Z : public X, public Y {
> >     public:
> >         using X::x;
> >     };
> >
> >     int main() {
> >         Z z;
> >         assert( z.x() == 'X' );
> >     }
> >
> > My second question is, Does the Standard allow this behavior?
>
> Yes.  The using-declaration hides Y::x(), and there's only one
> subobject of type X in Z, so there's no ambiguity.

Do all of the effects of the using declaration on member lookup boil down to
effects on name hiding?  In the first example, the using declarations pull
C::x(int) and A::x() into the scope of D (in a sense), preventing
D::x(double) from hiding them.  In the second, the using declaration pulls
X::x into the scope of Z, causing X::x to hide Y::x instead of conflicting
with it.

When you mention the effect of using declarations on overload resolution,
are you referring to its indirect effect, through its influence on the
composition of the lookup set?

Thanks.
__________________________
William Hayes
Raven Software Limited

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: wmm@world.std.com ("William M. Miller")
Date: Sat, 12 Jul 2003 00:15:20 +0000 (UTC)
Raw View
""William Hayes"" <whayes@hfx.andara.com> wrote in message
news:otgPa.40299$PD3.175106@nnrp1.uunet.ca...

> Do all of the effects of the using declaration on member lookup boil down
to
> effects on name hiding?  In the first example, the using declarations pull
> C::x(int) and A::x() into the scope of D (in a sense), preventing
> D::x(double) from hiding them.  In the second, the using declaration pulls
> X::x into the scope of Z, causing X::x to hide Y::x instead of conflicting
> with it.

Yes.  Of course, there are also effects on access and overloading,
but in terms of name lookup, it's just for circumventing hiding.
(It's different with namespace using-declarations, where visibility
is the principal effect, but since base class members are visible
in derived classes unless hidden, for member using-declarations it's
all about avoiding hiding.)

> When you mention the effect of using declarations on overload resolution,
> are you referring to its indirect effect, through its influence on the
> composition of the lookup set?

No, I was thinking of 7.3.3p13 -- the implicit "this" parameter of
a non-static member function is assumed to be the type of the class
in which the using-declaration appears, not that of the class of
which it is directly a member.  Here's an example where that makes
a difference:

    struct B {
        void f(char);
    };
    struct D: B {
        using B::f;
        void f(int);
    };
    void x(D* dp) {
        dp->f('c');
    }

If it weren't for this rule, the call "dp->f('c')" would be
ambiguous -- the 'c' is a better match for B::f(char), but "dp" would
be a better match for D::f(int).  Because of the using-declaration,
B::f(char) is treated for overload resolution as if its "this" were
a D* instead of a B*, allowing the two member functions to compete
solely on the basis of their explicit parameter types.

-- William M. Miller


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: wmm@world.std.com ("William M. Miller")
Date: Wed, 9 Jul 2003 22:03:20 +0000 (UTC)
Raw View
""William Hayes"" <whayes@hfx.andara.com> wrote in message
news:jFXMa.33361$PD3.111394@nnrp1.uunet.ca...
> The Standard says that "...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" (7.3.3, 14).  The example it cites involves an
> inheritance hierarchy containing multiple copies of a base class:
>
>     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
>     }
>
> My first question is, Why can't the using declaration be used to perform
> this kind of disambiguation?  If the using declaration declares function x
> in struct D as a "synonym" for C::x, it seems that it would be reasonable
to
> expect that d->x() would call C::x, just as d->C::x() would.

The only effect of a using-declaration (apart from overload
resolution) is to prevent hiding of base class names by
derived class declarations.  It doesn't do anything else.
In particular, the two using-declarations in the example
above just mean that A::x() is not hidden by the declarations
in C and D.  However, there are two subobjects of type A, so
the lookup set has two A::x() members.  (See 10.2p2: "Each of
these declarations that was introduced by a using-declaration
is considered to be from each sub-object of C that is of the
type containing the declaration designated by the
using-declaration.")

Another way of looking at this is that using-declarations
deal only with types, not objects (cf "each sub-object ...
of the _type_ containing the declaration..." in the preceding
citation [emphasis added]).

> In spite of the Standard's general refusal to allow the using declaration
to
> resolve inherited member ambiguities, several compilers (Comeau 4.3.0.1,
g++
> 3.2, Borland 5.5.1) allow the following:
>
>     #include <cassert>
>
>     class X { public: char x(){ return 'X';} };
>     class Y { public: char x(){ return 'Y';} };
>     class Z : public X, public Y {
>     public:
>         using X::x;
>     };
>
>     int main() {
>         Z z;
>         assert( z.x() == 'X' );
>     }
>
> My second question is, Does the Standard allow this behavior?

Yes.  The using-declaration hides Y::x(), and there's only one
subobject of type X in Z, so there's no ambiguity.

-- William M. Miller


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: google@vandevoorde.com (Daveed Vandevoorde)
Date: Thu, 10 Jul 2003 00:41:34 +0000 (UTC)
Raw View
whayes@hfx.andara.com ("William Hayes") wrote:
> The Standard says that "...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" (7.3.3, 14).  The example it cites involves an
> inheritance hierarchy containing multiple copies of a base class:
>
>     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
>     }
>
> My first question is, Why can't the using declaration be used to perform
> this kind of disambiguation?  If the using declaration declares function x
> in struct D as a "synonym" for C::x, it seems that it would be reasonable to
> expect that d->x() would call C::x, just as d->C::x() would.

A using declaration is a mechanism to direct lookup.  To process the
expression "d->x()" a compiler has to do several things, among which:
   (a) look up x.
   (b) perform overload resolution if there are multiple x.
   (c) convert d to the appropriate type to serve as a "this"
        pointer for the selected x.

Using declarations only affect (a); not (c) (or (b), obviously).

In you example, the using declarations cause the overload set (i.e.,
the result of looking up x) to consist of:
    A::x()
    B::x(int)
    C::x(double)
Overload resolution easily picks A::x(), which means that d must
be converted to an A*, and it is that part that is ambiguous.

> In spite of the Standard's general refusal to allow the using declaration to
> resolve inherited member ambiguities, several compilers (Comeau 4.3.0.1, g++
> 3.2, Borland 5.5.1) allow the following:
>
>     #include <cassert>
>
>     class X { public: char x(){ return 'X';} };
>     class Y { public: char x(){ return 'Y';} };
>     class Z : public X, public Y {
>     public:
>         using X::x;
>     };
>
>     int main() {
>         Z z;
>         assert( z.x() == 'X' );
>     }
>
> My second question is, Does the Standard allow this behavior?

Sure, applying the principles above, the overload set is a singleton:
    X::x()
Overload resolution is trivial, and the conversion of the Z& to X& is
unambiguous.

        Daveed

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: whayes@hfx.andara.com ("William Hayes")
Date: Thu, 10 Jul 2003 17:42:52 +0000 (UTC)
Raw View
"Daveed Vandevoorde" <google@vandevoorde.com> wrote in message
news:52f2f9cd.0307091410.785d9b5b@posting.google.com...
> whayes@hfx.andara.com ("William Hayes") wrote:
> > The Standard says that "...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" (7.3.3, 14).  The example it cites involves an
> > inheritance hierarchy containing multiple copies of a base class:
> >
> >     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
> >     }
> >
> > My first question is, Why can't the using declaration be used to perform
> > this kind of disambiguation?  If the using declaration declares function
x
> > in struct D as a "synonym" for C::x, it seems that it would be
reasonable to
> > expect that d->x() would call C::x, just as d->C::x() would.
>
> A using declaration is a mechanism to direct lookup.  To process the
> expression "d->x()" a compiler has to do several things, among which:
>    (a) look up x.
>    (b) perform overload resolution if there are multiple x.
>    (c) convert d to the appropriate type to serve as a "this"
>         pointer for the selected x.
>
> Using declarations only affect (a); not (c) (or (b), obviously).
>
> In you example, the using declarations cause the overload set (i.e.,
> the result of looking up x) to consist of:
>     A::x()
>     B::x(int)
>     C::x(double)
> Overload resolution easily picks A::x(), which means that d must
> be converted to an A*, and it is that part that is ambiguous.
>
> > In spite of the Standard's general refusal to allow the using
declaration to
> > resolve inherited member ambiguities, several compilers (Comeau 4.3.0.1,
g++
> > 3.2, Borland 5.5.1) allow the following:
> >
> >     #include <cassert>
> >
> >     class X { public: char x(){ return 'X';} };
> >     class Y { public: char x(){ return 'Y';} };
> >     class Z : public X, public Y {
> >     public:
> >         using X::x;
> >     };
> >
> >     int main() {
> >         Z z;
> >         assert( z.x() == 'X' );
> >     }
> >
> > My second question is, Does the Standard allow this behavior?
>
> Sure, applying the principles above, the overload set is a singleton:
>     X::x()
> Overload resolution is trivial, and the conversion of the Z& to X& is
> unambiguous.

So, class member lookup returns (in effect) a list of qualified class member
names.  Each name on the list specifies a class that actually contains a
declaration of that member (e.g., A::x), never a derived class that merely
inherited one (e.g., C::x).  Is that correct?

Does the prohibition against using the using declaration for this kind of
disambiguation therefore reflect the way name lookup works, rather than a
design decision related to the using declaration itself?

Thanks.
__________________________
William Hayes
Raven Software Limited

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: whayes@hfx.andara.com ("William Hayes")
Date: Thu, 3 Jul 2003 18:26:51 +0000 (UTC)
Raw View
The Standard says that "...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" (7.3.3, 14).  The example it cites involves an
inheritance hierarchy containing multiple copies of a base class:

    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
    }

My first question is, Why can't the using declaration be used to perform
this kind of disambiguation?  If the using declaration declares function x
in struct D as a "synonym" for C::x, it seems that it would be reasonable to
expect that d->x() would call C::x, just as d->C::x() would.

In spite of the Standard's general refusal to allow the using declaration to
resolve inherited member ambiguities, several compilers (Comeau 4.3.0.1, g++
3.2, Borland 5.5.1) allow the following:

    #include <cassert>

    class X { public: char x(){ return 'X';} };
    class Y { public: char x(){ return 'Y';} };
    class Z : public X, public Y {
    public:
        using X::x;
    };

    int main() {
        Z z;
        assert( z.x() == 'X' );
    }

My second question is, Does the Standard allow this behavior?

Thanks,

William Hayes

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]