Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

overload question (Alexandrescu article)

97 views
Skip to first unread message

Patrick Rabau

unread,
Dec 18, 2002, 11:01:51 AM12/18/02
to
In the latest article of Andrei Alexandrescu on www.cuj.com, he makes
use of a clever overloading technique. I just can't figure out why it
works. Here is a distilled version of the problem.

class A {};
class B : public A {};

void f(A);
void f(B);

class C {
public:
operator A() const { return A(); };
operator B() { return B(); };
};

void foo()
{
C c;
f(c); // (1)
}

The call f(c) in (1) will call f(B). Both g++ and Comeau C++ agree
(but the latest Sun CC 5.4 thinks it's ambiguous).

Why is (1) not an ambiguous call between f(A) and f(B)?

Note that for (1) not to be ambiguous requires two things:
- B is derived from A
- C::operator A() is const

Why?

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]

Hyman Rosen

unread,
Dec 18, 2002, 1:49:22 PM12/18/02
to
Patrick Rabau wrote:
> Why is (1) not an ambiguous call between f(A) and f(B)?

The compiler needs to choose between calling one of the
two member functions, one which takes 'C *' as its 'this'
parameter, and one which takes a 'C const *' as its 'this'
parameter. Since 'c' is not const, the non-const member
is a better match.

John Potter

unread,
Dec 18, 2002, 6:07:23 PM12/18/02
to
On 18 Dec 2002 13:49:22 -0500, Hyman Rosen <hyr...@mail.com> wrote:

> Patrick Rabau wrote:
> > Why is (1) not an ambiguous call between f(A) and f(B)?
>
> The compiler needs to choose between calling one of the
> two member functions, one which takes 'C *' as its 'this'
> parameter, and one which takes a 'C const *' as its 'this'
> parameter. Since 'c' is not const, the non-const member
> is a better match.

That does not answer the question. Those things are still
true if the operator B is const and the operator A is not,
but it is then ambiguous. Those things are still true when
B is not derived from A, but it is then ambiguous.

John

Patrick Rabau

unread,
Dec 19, 2002, 12:05:57 AM12/19/02
to
Hyman Rosen <hyr...@mail.com> wrote in message
news:<10402304...@master.nyc.kbcfp.com>...

> Patrick Rabau wrote:
> > Why is (1) not an ambiguous call between f(A) and f(B)?
>
> The compiler needs to choose between calling one of the
> two member functions, one which takes 'C *' as its 'this'
> parameter, and one which takes a 'C const *' as its 'this'
> parameter. Since 'c' is not const, the non-const member
> is a better match.

I don't think that's the correct reason. If you make class B
not derived from class A, your argument still stands. Yet the
call f(c) becomes ambiguous.

Nikolai Borissov

unread,
Dec 19, 2002, 6:21:33 AM12/19/02
to
Patrick Rabau wrote :
<SNIP>

> class A {};
> class B : public A {};
>
> void f(A);
> void f(B);
>
> class C {
> public:
> operator A() const { return A(); };
> operator B() { return B(); };
> };
>
> void foo()
> {
> C c;
> f(c); // (1)
> }
>
> The call f(c) in (1) will call f(B). Both g++ and Comeau C++ agree
> (but the latest Sun CC 5.4 thinks it's ambiguous).
>
> Why is (1) not an ambiguous call between f(A) and f(B)?

Apparently the first cast (operator A() const...) is not considered in this
case.
The rule would sound like this:
** if non-const cast to derived class is applicable then const casts to any
of its base classes are not considered. **
I don't know what is the rationale here.

Nikolai Borissov

Daveed Vandevoorde

unread,
Dec 20, 2002, 12:48:13 PM12/20/02
to
pra...@att.net (Patrick Rabau) wrote:
> In the latest article of Andrei Alexandrescu on www.cuj.com, he makes
> use of a clever overloading technique. I just can't figure out why it
> works. Here is a distilled version of the problem.
>
> class A {};
> class B : public A {};
>
> void f(A);
> void f(B);
>
> class C {
> public:
> operator A() const { return A(); };
> operator B() { return B(); };
> };
>
> void foo()
> {
> C c;
> f(c); // (1)
> }
>
> The call f(c) in (1) will call f(B). Both g++ and Comeau C++ agree
> (but the latest Sun CC 5.4 thinks it's ambiguous).
>
> Why is (1) not an ambiguous call between f(A) and f(B)?

It's a good (read: difficult ;-) question.
I couldn't figure it out, so I ended up asking
Steve "Mr. Overload" Adamczyk. I've got to say:
Overload resolution is the most complex part of
the language IMO (worse than lookup or template
issues if you ask me).

There is an appendix on overload resolution in
"C++ Templates." While it is more extensive than
other discussions I've read in books, it's still
not sufficient to figure out this problem.

OK, so we have two candidates: f(A) and f(B).
f(B) is obviously viable (meaning, it could be
called if we selected it) by the conversion
sequence C -> B.

f(A) is trickier. There are _two_ conversion
sequences:
C -> C const -> A
C -> B -> A
Now a recursive overload resolution problem is
posed (as an initialization issue; 13.3.1.5) to
select the appropriate user-defined conversion.
(That's the part not covered by "C++ Templates").
What is slightly quirky here (but known by the
few overload-resolution gurus in the committee)
is that this only involves the part of the
sequence up to the user-defined conversion.
In other words, we compare
C -> C const
with
C -> C
and ignore the B->A part needed for the second
option. Based on those two, the second is preferred.

Now back to the original overload resolution
problem. We have two viable functions with
associated conversion sequences
f(A): C -> B -> A
and
f(B): C -> B
Here the "subsequence principle" plays: An
implicit conversion sequence is preferred
over another one if it is a subsequence of
that other one (13.3.3.2/3).

And so you end up calling f(B).



> Note that for (1) not to be ambiguous requires two things:
> - B is derived from A
> - C::operator A() is const

Indeed. If B is not derived from A, f(A)
is viable only through the conversion sequence
C -> C const -> A, which is neither better not
worse than C -> B. If C::operator A() is
non-const, then the two conversion sequences
for f(A) are equally good and 13.3.3.1/10
comes into play, which makes any call requiring
a user-defined conversion ambiguous (because
one of the candidates requiring a user-defined
conversion has a so-called "ambiguous conversion
sequence").

Daveed

Nikolai Borissov

unread,
Dec 20, 2002, 10:00:10 PM12/20/02
to
Nikolai Borissov wrote :

> The rule would sound like this:
> ** if non-const cast to derived class is applicable then const casts to
any
> of its base classes are not considered. **

A correction to the rule:
** if non-const cast to a derived class is applicable then const casts to
BOTH THE DERIVED CLASS AND any of its base classes are not considered **

Andrei Alexandrescu

unread,
Dec 21, 2002, 6:27:06 PM12/21/02
to
"Daveed Vandevoorde" <goo...@vandevoorde.com> wrote in message
news:52f2f9cd.02122...@posting.google.com...
[snip - but do read that one, it's great!]

Thanks, Daveed.

With my heart in my hand, I publically admit I did NOT have any idea on
why
that works when I wrote the article. I did have a high degree of
confidence
that it does work, because I tried it with today's trustiest compilers,
and
they all agreed.

For the record, Dave Abrahams proposed this trick to me - without
explaining
why it works :o). Before that, I was using an uglier scheme with some
casts.

Speaking of Mojo - I am getting a ***flood*** of emails with proposals
for
making Mojo better. Reviewing them takes quite some time because most
ideas
are nontrivial. Mojo proved to be quite stable because it had considered
most of the alternatives and rejected them after deliberation. Usually
solutions are better in some aspects and fail in others. Again, Mojo's
design goals were (in this order): (1) 100% replacement of unnecessary
copying with moving/swapping, guaranteed (portable across compilers);
(2) As
much transparency as possible - minimum impact on legacy code that's to
be
changed to use mojo.

My feeling is that Mojo can be nicely improved by using #ifdef to
tailor it
for various concrete compilers, depending on how good they are at
optimizing
away temporary objects. Mojo does slightly more work than a
well-implemented
RVO.

For Mojo's stability I would like to thank again to the many, many
volunteer
reviewers on this newsgroup. If I failed to mention anybody who has
sent me
feedback, I apologize; please send me email and I'll fix that.

http://moderncppdesign.com/publications/cuj-02-2003.html


Andrei

P.S. Ah, and the "Mojo sucks" email hasn't arrived yet :o).

Patrick Rabau

unread,
Dec 22, 2002, 9:11:46 AM12/22/02
to
goo...@vandevoorde.com (Daveed Vandevoorde) wrote in message news:<52f2f9cd.02122...@posting.google.com>...
> pra...@att.net (Patrick Rabau) wrote:

{over quote snipped by moderator -mod/fwg}

> If C::operator A() is
> non-const, then the two conversion sequences
> for f(A) are equally good and 13.3.3.1/10
> comes into play, which makes any call requiring
> a user-defined conversion ambiguous (because
> one of the candidates requiring a user-defined
> conversion has a so-called "ambiguous conversion
> sequence").

Let me make sure I correctly understand this last case.

If C::operator A() is non-const, the two conversion sequences for
f(A) are:
C -> A


C -> B -> A

Normally the first sequence would be preferred because it is
a subsequence of the second one. However, we are in the "quirky
case" of recursive overload resolution you mention above. So we
should only look at the parts of the sequences before the user
conversions, and they are both the same: C -> C
That's why the two conversion sequences for f(A) are equally good
and 13.3.3.1/10 applies.
Is that what you had in mind?

Which section of the Standard mentions this "quirky case"?

Patrick

Daveed Vandevoorde

unread,
Dec 23, 2002, 12:28:58 AM12/23/02
to
pra...@att.net (Patrick Rabau) wrote:

> goo...@vandevoorde.com (Daveed Vandevoorde) wrote:
> > pra...@att.net (Patrick Rabau) wrote:
>
> {over quote snipped by moderator -mod/fwg}
>
> > If C::operator A() is
> > non-const, then the two conversion sequences
> > for f(A) are equally good and 13.3.3.1/10
> > comes into play, which makes any call requiring
> > a user-defined conversion ambiguous (because
> > one of the candidates requiring a user-defined
> > conversion has a so-called "ambiguous conversion
> > sequence").
>
> Let me make sure I correctly understand this last case.
>
> If C::operator A() is non-const, the two conversion sequences for
> f(A) are:
> C -> A
> C -> B -> A

Yes.

> Normally the first sequence would be preferred because it is
> a subsequence of the second one.

No. C->B and B->A would be subsequences of C->B->A,
but C->A is not a subsequence of C->B->A.

> However, we are in the "quirky
> case" of recursive overload resolution you mention above. So we
> should only look at the parts of the sequences before the user
> conversions, and they are both the same: C -> C

That part is correct.

> That's why the two conversion sequences for f(A) are equally good
> and 13.3.3.1/10 applies.
> Is that what you had in mind?

Yes.

> Which section of the Standard mentions this "quirky case"?

8.5/14 (the most complex paragraph in the standard IMO) and
13.3.1.5/2. The relevant concepts here are that (a) the
conversion functions are enumerated, and (b) the best conversion
is selected using overload resolution rules on the argument
(passed as *this) of those conversion functions. The second
part of the conversion does not come into play while selecting
the best conversion function (part b); only while enumerating
the conversion functions (part a).

Daveed

David Abrahams

unread,
Jan 30, 2003, 7:33:11 AM1/30/03
to
goo...@vandevoorde.com (Daveed Vandevoorde) writes:

> f(A) is trickier. There are _two_ conversion
> sequences:
> C -> C const -> A
> C -> B -> A
> Now a recursive overload resolution problem is
> posed (as an initialization issue; 13.3.1.5) to
> select the appropriate user-defined conversion.

I've been trying to figure out how that section of the standard
applies for weeks now. It begins,

"Under the conditions specified in 8.5,
as part of an initialization of an object of nonclass type..."

Since we are only initializing objects of class type here, AFAICT,
isn't that paragraph ruled out?

> (That's the part not covered by "C++ Templates").
> What is slightly quirky here (but known by the
> few overload-resolution gurus in the committee)
> is that this only involves the part of the
> sequence up to the user-defined conversion.
> In other words, we compare
> C -> C const
> with
> C -> C
> and ignore the B->A part needed for the second
> option. Based on those two, the second is preferred.
>
> Now back to the original overload resolution
> problem. We have two viable functions with
> associated conversion sequences
> f(A): C -> B -> A
> and
> f(B): C -> B
> Here the "subsequence principle" plays: An
> implicit conversion sequence is preferred
> over another one if it is a subsequence of
> that other one (13.3.3.2/3).
>
> And so you end up calling f(B).

This came up because, as Andrei indicated, we started with some code
which looked like this but didn't have B derived from A, and there was
an ambiguity. My intuition said that making f(B) a "more-specific"
match than f(A) would help resolve the ambiguity. I had some notion
that the subsequence rule came into play here, but needless to say I
didn't know what was actually going on.

It seems that you're saying what actually happened is different: the
inheritance allowed a new conversion path through B from C to f(A) and
effectively cut off the C -> C const -> A calling f(A) sequence from
being considered. That path had been causing an ambiguity Since C ->
B calling f(B) is not a subsequence of that path.

Did I get that right? From your remarks below it looks that way.

>> Note that for (1) not to be ambiguous requires two things:
>> - B is derived from A
>> - C::operator A() is const
>
> Indeed. If B is not derived from A, f(A)
> is viable only through the conversion sequence
> C -> C const -> A, which is neither better not
> worse than C -> B. If C::operator A() is
> non-const, then the two conversion sequences
> for f(A) are equally good and 13.3.3.1/10
> comes into play, which makes any call requiring
> a user-defined conversion ambiguous (because
> one of the candidates requiring a user-defined
> conversion has a so-called "ambiguous conversion
> sequence").

--
David Abrahams
da...@boost-consulting.com * http://www.boost-consulting.com
Boost support, enhancements, training, and commercial distribution

0 new messages