Should the following code compile according to the standard? I have
different results with different compilers.
Thank you,
Lucian Radu Teodorescu
#include <iostream>
using namespace std;
struct Target;
struct Source;
struct Target
{
Target() {}
Target(const Source& other)
{
cout << "Target: conversion constructor" << endl;
}
};
struct Source
{
Source() {}
operator Target ()
{
cout << "Source: conversion operator" << endl;
return Target();
}
};
int main(int argc, char* argv[])
{
Target t;
Source s;
t = s;
return 0;
}
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
It should, IMHO.
> I have different results with different compilers.
What error message(s) do you get?
> int main(int argc, char* argv[])
> {
> Target t;
> Source s;
>
> t = s;
>
> return 0;
> }
This compiles cleanly on my system:
$ g++ -Wall source-target.cpp
$ ./a
Source: conversion operator
$ g++ -v
Reading specs from ...
[skipped]
Thread model: win32
gcc version 3.3.1 (mingw special 20030804-1)
$
Source::operator Target() is called. But did you really expect a call
to Target's constructor in the 't=s' line?
Thanks.
Alex
The compiler's not sure whether to use the Target constructor or the
conversion operator. Remove one or the other.
with VC++ 2005 i get on the "t = s" line:
d:\work\test\test\test.cpp(34) : error C2679: binary '=' : no operator
found which takes a right-hand operand of type 'Source' (or there is no
acceptable conversion)
d:\work\test\test\test.cpp(16): could be 'Target
&Target::operator =(const Target &)'
while trying to match the argument list '(Target, Source)'
I've also tried the code on Drinkumware Exam (online compilation) and
here are the results:
VC++ 8: Fail
VC++ 7.1: Fail
VC++ 6: Fail
EDG/C++: Success
MINGW: Success with warnings:
"sourceFile.cpp: In
function `int main(int, char**)':
sourceFile.cpp:35: warning: choosing
`Source::operator Target()' over `Target::Target(const Source&)'
sourceFile.cpp:35: warning:
for conversion from `Source' to `Target'
sourceFile.cpp:35: warning:
because conversion sequence for the argument is better"
> Source::operator Target() is called. But did you really expect a call
> to Target's constructor in the 't=s' line?
>
I didn't find in the standard very clearly if there is an ambiguity or
not. And if there is no ambiguity what conversion path is choosen.
Thank you
[Snip classes with both T.T(const S &), and S.operator T() defined and
then ...]
> > Target t;
> > Source s;
> > t = s;
>
> It should, IMHO.
Why? It looks ambigous to me
My appologies, I did not notice that compiler may construct temporary
Target object in that line. So there is ambiguity, since compiler have
two ways to pass 'Target const&' to Target::operator=(Target const&):
t.Target::operator=(s.Source::operator Target());
t.Target::operator=(Target(s));
right?
Alex
> [Snip classes with both T.T(const S &), and S.operator T() defined and
> then ...]
> > > Target t;
> > > Source s;
> > > t = s;
> > It should, IMHO.
> Why? It looks ambigous to me
I don't think so. All other things being equal, the compiler
should prefer to pass a non-const object to a non-const
reference or call a non-const function on it. In this case, all
other things are equal, so the compiler should prefer the
non-const operator Target() function to binding s to the S
const& parameter of the constructor.
Of course, such things are very subtle, and likely to confuse
the reader, even in cases where the compiler gets it right. As
a general rule, circular implicit conversions should be avoided,
as they tend to be a source of problems and ambiguities.
--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
User-defined conversions are considered only if they are necessary to
resolve a call.
> >> [Snip classes with both T.T(const S &), and S.operator T() defined and
> >> then ...]
> >>>> Target t;
> >>>> Source s;
> >>>> t = s;
> >>> It should, IMHO.
> >> Why? It looks ambigous to me
> > I don't think so. All other things being equal, the compiler
> > should prefer to pass a non-const object to a non-const
> > reference or call a non-const function on it. In this case, all
> > other things are equal, so the compiler should prefer the
> > non-const operator Target() function to binding s to the S
> > const& parameter of the constructor.
> User-defined conversions are considered only if they are
> necessary to resolve a call.
Which is the case here. The only available operator= requires a
Target on the right hand side, and there is no standard
conversion sequence which will convert a Source to a Target.
The only question is which user-defined conversion to use:
Target::Target(Source const&), or Source::operator Target().
The first binds a non-const object to a const reference, and the
second uses the non-const object to call a non-const function
(which, according to §13.3.1/4, acts exactly as if we were
binding to a non-const reference). As far as I can see, this is
the *only* difference in the two possibilities. And that
difference is covered in §13.3.3.2/3, fifth bullet: Target
const& is more cv-qualified than Target& (the implicit parameter
of Target::operator Target()), so Target& is a better match.
(But I'm not 100% sure that this rule applies here.)
--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
I believe that we have the following scenario:
Target::operator = (const Target&) is the only viable function to be
called, and the problem is what conversion is performed to convert
Source to Target.
The difference you spoted is only relevant when the compiler is trying
to distinguish between two viable functions, and not when
distinguishing between two user-defined conversion sequences. If we
have had two candidate functions to distinguish from, according to
13.3.3.1.2/1 and 13.3.3.2/3, this difference regards the initial
standard conversion sequence which is ignored when comparing
user-defined conversion sequences. Besides this the 13.3.3.2/3 rule
applies only if the converions constructor or operator are the same in
both user-defined conversions. I don't think this is our case.
Now I found that 1.3.3.1/10 describes exactly this case: several
different sequences for conversion are found, so this is an ambiguous
conversion sequence. If this is used when selecting the best viable
function, the call will be ill-formed.
Am I correct?
> I believe that we have the following scenario:
> Target::operator = (const Target&) is the only viable function to be
> called, and the problem is what conversion is performed to convert
> Source to Target.
That much is sure.
> The difference you spoted is only relevant when the compiler
> is trying to distinguish between two viable functions, and not
> when distinguishing between two user-defined conversion
> sequences. If we have had two candidate functions to
> distinguish from, according to 13.3.3.1.2/1 and 13.3.3.2/3,
> this difference regards the initial standard conversion
> sequence which is ignored when comparing user-defined
> conversion sequences. Besides this the 13.3.3.2/3 rule applies
> only if the converions constructor or operator are the same in
> both user-defined conversions. I don't think this is our case.
I'll admit that I'm not at all sure. Overload resolution is one
of the most complex parts of the standard. From what I see:
-- Given that there is only one function which can be called,
we are trying to initialize its const reference parameter.
We start in §8.5.3, and find that "Otherwise [our case], a
temporary of type 'cv1 T1'' is created and initialized from
the initializer expression using the rules for a
non-reference copy initialization."
-- This takes us to §8.5/14, fourth bullet, third sub-bullet:
"Otherwise (i.e., for the remaining copy-initialization
cases), user defined conversion sequences which can convert
from the source type to the destination type or (when a
conversion function is used) to a derived class thereof are
enumerated as described in 13.3.1.4, and the best one is
chosen through overload resolution."
-- This brings us to §13.3.1.4, where it explicitely says that
both converting constructors and conversion functions are
involved. At that point, we have an overload set of two
functions, the constructor, and the conversion operator,
each with a single parameter. Since both are viable, we
skip through §13.3.2.
-- In §13.3.3, we start by finding the best match for the
argument we are given. For both functions, we have a user
defined conversion sequence. (In §13.3.3.1/3, exactly three
conversion sequences are listed. In neither case do we have
an ellipsis conversion sequence, and neither is a standard
conversion sequence. Both are thus user-defined conversion
sequences.
-- According to §13.3.3.1.2, a user-defined conversion sequence
consists of three parts; an initial standard conversion
sequence, the user defined conversion, and a final standard
conversion sequence. In our case, only the first differs.
-- There are some special rules in §13.3.3.1.4 which apply to
reference binding; the implicit first parameter is
assimilated to a reference here, with a few differences (not
relevant in our case). In the end, both functions rank
equal.
-- Which brings us to §13.3.3.2/3, fourth bullet: "S1 and S2
differ only in their qualification conversion and yield
similar types T1 and T2, respectively, and the
cv-qualification signature of type T1 is a proper subset of
the cv-qualification signature of type T2." In the case of
the constructor, the signature is Source const&, and in the
case of the conversion function, the first parameter is
assimilated to Source&. So the conversion function wins.
> Now I found that 1.3.3.1/10 describes exactly this case:
> several different sequences for conversion are found, so this
> is an ambiguous conversion sequence.
Two are found. Both are user defined, i.e. functions, so
overload resolution is then applied to them.
> If this is used when selecting the best viable function, the
> call will be ill-formed.
> Am I correct?
I don't think so, but I would feel better about it if a real
expert were to pontify. I know that all of §13 is very, very
difficult to interpret.
--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
Ok ... fine so far
> -- Which brings us to §13.3.3.2/3, fourth bullet: "S1 and S2
> differ only in their qualification conversion and yield
> similar types T1 and T2, respectively, and the
> cv-qualification signature of type T1 is a proper subset of
> the cv-qualification signature of type T2." In the case of
> the constructor, the signature is Source const&, and in the
> case of the conversion function, the first parameter is
> assimilated to Source&. So the conversion function wins.
>
I agree with you that the *first standard conversion sequence* of the
conversion function is better than the *first standard conversion
sequence * of the constructor. But as you said before "In our case,
only the first differs", and looking to §13.3.3.2/3 second (major)
bullet, I see that only the second standard conversion sequence
matters. So, I don't see the reason why the conversion operator is
choosen over the conversion constructor.
> > Now I found that 1.3.3.1/10 describes exactly this case:
> > several different sequences for conversion are found, so this
> > is an ambiguous conversion sequence.
>
> Two are found. Both are user defined, i.e. functions, so
> overload resolution is then applied to them.
>
> > If this is used when selecting the best viable function, the
> > call will be ill-formed.
>
> > Am I correct?
>
> I don't think so, but I would feel better about it if a real
> expert were to pontify. I know that all of §13 is very, very
> difficult to interpret.
>
I'm not good at interpreting the standard, so please bear with me if I
interpreted something wrong from the standard.
Maybe we should move this to comp.std.c++ ....
Thank you very much
[...]
> Ok ... fine so far
> > -- Which brings us to §13.3.3.2/3, fourth bullet: "S1 and S2
> > differ only in their qualification conversion and yield
> > similar types T1 and T2, respectively, and the
> > cv-qualification signature of type T1 is a proper subset of
> > the cv-qualification signature of type T2." In the case of
> > the constructor, the signature is Source const&, and in the
> > case of the conversion function, the first parameter is
> > assimilated to Source&. So the conversion function wins.
> I agree with you that the *first standard conversion sequence* of the
> conversion function is better than the *first standard conversion
> sequence * of the constructor.
It has the same rank. But the entire conversion sequence of the
conversion function is a proper subset of the entire conversion
sequence of the constructor.
> But as you said before "In our case,
> only the first differs", and looking to §13.3.3.2/3 second (major)
> bullet, I see that only the second standard conversion sequence
> matters. So, I don't see the reason why the conversion operator is
> choosen over the conversion constructor.
I'm not sure. I'm a bit confused by the nesting of bullets
here. (There's a page break which makes it impossible to
compare the indentation, and the same bullet character is used
for several levels.) Looking more carefully, I think that there
are three main bullets, starting "Standard conversion sequence
S1 [...]", "S1 and S2 are reference bindings [...]", and
"User-defined conversion sequence U1 [...]". If so, however,
none applies, since for 1) we're not talking about a standard
conversion sequence, for 2) nor about reference binding, and for
3), the text says "[...] if they contain the SAME user-defined
conversion function or constructor [...]", and they don't.
I don't think it is at all clear as to whether the standard
conversion sequences in a user defined conversion are involved
in rankings. I think that the intent is that they are:
basically, overload resolution for the operator= ends up with
two different user-defined conversions, i.e. two different
functions, and overload resolution kicks in recursively to
choose which one. But I agree that it is far from clear.
[...]
> > I don't think so, but I would feel better about it if a real
> > expert were to pontify. I know that all of §13 is very, very
> > difficult to interpret.
> I'm not good at interpreting the standard, so please bear with
> me if I interpreted something wrong from the standard.
> Maybe we should move this to comp.std.c++ ....
That's what I'm thinking. None of the experts seem to be
responding here.
--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34