Topic: C++ Standards Question: Conversion Functions
Author: Bob Hathaway <rjh@geodesic.com>
Date: 1995/04/20 Raw View
In article <D7Azt3.LMB@midway.uchicago.edu>,
Bob Hathaway <rjh@geodesic.com> wrote:
>In article <AKV.95Apr11093358@srl03.cacs.usl.edu>,
>Anil Vijendran <akv@cacs.usl.edu> wrote:
>>
>>> On Fri, 31 Mar 1995 19:22:50 GMT, Bob Hathaway <rjh@geodesic.com> said:
>>>...
>>...
>...
> are not aware (but this is an error), and that a change to this stealth
> (which should never occur) conversion interface would change users
> code (simply incorrect in the direct case] - but so what if the user's
> defect is mildly changed; its still wrong, whether thru an incorrect
> constructor or the addition or change of several! Admittedly, I am
I think I've finally thought of the *only* two real cases where a problem
can occur. I now ignore the unknown classes error pointed out in the C++
Report because a competent programmer will correctly choose his arguments
(and know his classes!) These changes are similar to changes in conversion
functions, but I'll cover them anyway, so if the following change(s) occur
in a derived class of the formal then no error or waring will occur (and
these are the only two cases):
1) The original ctor is deleted and simultaneously *exactly* one matching
ctor is added (probably along a different inheritance path).
2) Another ctor is added along the same inheritance path.
CASE 1) This is obscure, but is it a problem?
person
/ \
boy girl -- girl(spirit&) // A reincarnation constructor, girls can be
// reincarnated.
... takesToDinner(person&); // A declaration that accepts a person that
// can be taken to dinner.
spirit freddyKruger; // Freddy is a spirit.
... takesToDinner(freddyKruger); // Freddy can become a girl and go to dinner
// (Yikes! Luckily this hasn't happened to
// me, although I may have met ...).
// This can't competently be a mistake;
// perhaps the maintanance programmer
// simply isn't cognizant of it.
// But it is a little tricky, though.
CLASS CODE CHANGE: I say spirits can't turn into girls anymore, but can turn
into boys.
person
/ \
boy(spirit) -- boy girl
Now, it is possible this change won't be known by the maintainer of the
above call to takesToDinner (or the call will be overlooked), although
good tools and standards would immediately point out the change. But calls
to the original conversion constructor must be intentional (there really
isn't any such thing as unknown classes when providing arguments, except
for blunders), and it should also be obvious what was intended. It is also
obvious that Freddy should now become a boy to attend dinner, an expected
change in the derived classes.
CASE 2) Another ctor is added along the same inheritance path.
animal (Abstract Class)
|
person (Abstract Class)
/ \
boy girl -- girl(spirit&)
\
giftedGirl -- giftedGirl(spirit&);
Which *should* be called?
1) The constructor closer to the formal's class (a closer D -> B match).
2) The more specialized constructor.
3) Neither, ambiguity error.
This seems a difficult question. I first stated conversion functions and conv
by ctor were inversely symmetrical, but now I'm not sure. Offhand, I'd guess
case 2) is best (the most specific match).
This example can also demonstrate case 1), the case of Freddy turning into
a killer dog to be taken to the park, which calls for an animal argument,
can only occur if the other constructors are removed simultaneously with
the addition of a giraffe conv ctor, which then obviously should occur, as
should the error that will correctly occur as Freddy can no longer go to
dinner.
Other issues are:
1) Overloaded functions cause far greater code changes far more easily.
2) Leaving out about half of all user-defined conversions in C++ seems
excessive. Is the cure worse than the disease?
3) Explicit conversions can be called when desired. This would eliminate
any problems and cause an error if the constructor interface were
changed (as does use of the new 'explicit' keyword.
And to stop playing Devil's advocate:
1) Unintentional matching of an actual to a formal could occur on a
constructor that was not intended for implicit conversion, but simply
allows one argument for construction, or when that constructor wasn't
intended. This would be an argument passing blunder, but should the
language enforce it not occuring? It will happen anyway if the
constructor is in the formal's class, perhaps with many default
arguments. But this is an obvious blunder and hasn't really been
brought up. This is the reason for code reviews.
2) Too many conversions can lead to the Ada use-clause syndrom, but the
new 'explicit' clause can help as would using explicit conversions.
Counter examples are most welcome (I am playing Devil's advocate), but I
think my time allotment on this issue is nearing an end. I really just
wanted to have the standard defined, and luckily there is a clear and
recent paper on the subject (although I'm not sure about their stated
example or reasons). And this has helped me to fully understand the issues
involved. Perhaps now that I think I can relax with a standard definition,
it'll change again!
Thanks and Best Regards,
bob
rjh@geodesic.com
Author: Bob Hathaway <rjh@geodesic.com>
Date: 1995/04/19 Raw View
In article <AKV.95Apr11093358@srl03.cacs.usl.edu>,
Anil Vijendran <akv@cacs.usl.edu> wrote:
>
>>>>>> On Fri, 31 Mar 1995 19:22:50 GMT, Bob Hathaway <rjh@geodesic.com> said:
>
> Bob> class A{};
> Bob> class Q{};
> Bob> class X: public Q{
> Bob> public:
> Bob> X(const A&);
> Bob> };
>
> Bob> void fn(Q&);
>
> Bob> int main(){
> Bob> A a;
> Bob> fn(a); // Compilers report an error.
> Bob> return 0;
> Bob> }
>
>This is how this should be handled:
>
>We need a type conversion from A to Q.
>
>Step 1. Look in the *scope* of A (the source type) if there are any
>user defined operator functions to convert to a Q or any class derived
>from Q. There are none in our case.
>
>Step 2. Look (*only*) in the target type (Q) if there is a constructor
>that takes an A or a base class of A as parameter. The classes derived
>from Q are not searched for such constructors.
>...
I've just read the article on conversion functions and it appears the
case under question, searching derived classes for implicit conversion
by constructor was outlawed by the ANSI committee. As another issue on
the construct validity of the decision,
They state:
User code silently depending on conversions from classes completely
unrelated to the classes they use.
???:
I would consider derived classes relevant to the classes users use*,
and had never considered slicing a hierarchy and only considering a
part of it. That is what browsers are for, displaying class
hierarchies and perhaps implicit conversions. And all changes should
always be checked for any effects they may have (although this could
be a problem in standardless settings).
*In a polymorphic (and reuse) oriented environment, user code could be
called with an object created out of their program's environment -
this is the greatest strength of the OO approach and is relevant to
overridable (virtual) functions, but not to statically typed conversion
functions. These can only be affected by source changes available to
programmers and included in their programs; always available to
programmers thru class browsers.
They state:
If the user's code silenty depends [on unknown classes, MAJOR error]
[...] changes to the interface of the conversion functions in the derived
classes could then change the meaning of the user's code [...] unpleasant
surprise for users.
???:
I've already pointed out that such interface changes can't effect
dependent user code and at worst (best?) would lead to ambiguity errors
forcing the user to explicitly perform the conversion. And my response
above points out that unknown classes shouldn't exist. And better,
they mention a user depending on such a silent conversion of which they
are not aware (but this is an error), and that a change to this stealth
(which should never occur) conversion interface would change users
code (simply incorrect in the direct case] - but so what if the user's
defect is mildly changed; its still wrong, whether thru an incorrect
constructor or the addition or change of several! Admittedly, I am
somewhat of an expert on this subject by trade, but this seems
obvious to me. If an argument is passed to a formal and requires any
kind of conversion, the programmer should know what they are doing and
be aware of any conversions that apply, a trivial check and the new
'explicit' keyword could also help by forcing explicit conversion. And
obviously any change to a header file should be checked for code changes,
standard SE practice.
But wait, what about the case brought up by others, that another
conversion in a derived class would be defined - what a fortuituos
happening for our hypothetical problematical programmer - as an
ambiguity error would now show up, revealing the original oversight, or
even better, the original defect!!! (Which again, should never have
occurred.) Is the committee being overprotective/restrictive in this
case? Perhaps with better standards and environments, could more
flexibility and power be the greater guiding principle?
Best Regards,
bob
rjh@geodesic.com
Author: akv@srl03.cacs.usl.edu (Anil Vijendran)
Date: 1995/04/11 Raw View
>>>>> On Fri, 31 Mar 1995 19:22:50 GMT, Bob Hathaway <rjh@geodesic.com> said:
Bob> class A{};
Bob> class Q{};
Bob> class X: public Q{
Bob> public:
Bob> X(const A&);
Bob> };
Bob> void fn(Q&);
Bob> int main(){
Bob> A a;
Bob> fn(a); // Compilers report an error.
Bob> return 0;
Bob> }
This is how this should be handled:
We need a type conversion from A to Q.
Step 1. Look in the *scope* of A (the source type) if there are any
user defined operator functions to convert to a Q or any class derived
from Q. There are none in our case.
Step 2. Look (*only*) in the target type (Q) if there is a constructor
that takes an A or a base class of A as parameter. The classes derived
from Q are not searched for such constructors.
This is roughly what I learnt from a recent article in the C++ report
(mail me if you need more details on the article).
Bob> In the call to fn above, shouldn't derived classes of Q be checked for
Bob> conversion by constructor, inversely symmetrical with base classes of A
Which would you chose if there is another
struct Y: Q
{
Y(const A&);
};
which *I* wrote after you had `fn' up and running?
Bob> I'm already aware of the ambiguity growth vs. expectancy vs. generality issues;
Bob> again, I'd just like to know the official story.
The article was from someone in the ANSI/ISO group.
Bob> Thank you in advance for any responses (directed to me or this newsgroup).
Bob> Best Regards,
Bob> bob
Bob> rjh@geodesic.com
--
Anil
___________________________________________________________________________
Anil K Vijendran USL Box 43007, Lafayette, LA 70504-3007
akv@cacs.usl.edu (318) 232-5502 [H]
Author: Bob Hathaway <rjh@geodesic.com>
Date: 1995/04/11 Raw View
In article <AKV.95Apr11093358@srl03.cacs.usl.edu>,
Anil Vijendran <akv@cacs.usl.edu> wrote:
>
>>>>>> On Fri, 31 Mar 1995 19:22:50 GMT, Bob Hathaway <rjh@geodesic.com> said:
>
> Bob> class A{};
> Bob> class Q{};
> Bob> class X: public Q{
> Bob> public:
> Bob> X(const A&);
> Bob> };
>
> Bob> void fn(Q&);
>
> Bob> int main(){
> Bob> A a;
> Bob> fn(a); // Compilers report an error.
> Bob> return 0;
> Bob> }
>
>This is how this should be handled:
>
>We need a type conversion from A to Q.
>
>Step 1. Look in the *scope* of A (the source type) if there are any
>user defined operator functions to convert to a Q or any class derived
>from Q. There are none in our case.
>
>Step 2. Look (*only*) in the target type (Q) if there is a constructor
>that takes an A or a base class of A as parameter. The classes derived
>from Q are not searched for such constructors.
>
>This is roughly what I learnt from a recent article in the C++ report
>(mail me if you need more details on the article).
I need more details; I haven't been keeping up with the C++ report. And
thank you very much in advance for the reference, I'm looking forward to
reading it. It does differ from the reference manuals if the standard
explicitly states derived classes of Q are not searched.
>
> Bob> In the call to fn above, shouldn't derived classes of Q be checked for
> Bob> conversion by constructor, inversely symmetrical with base classes of A
>
>Which would you chose if there is another
>
> struct Y: Q
> {
> Y(const A&);
> };
>
>which *I* wrote after you had `fn' up and running?
This is an ambiguity error. There is another thread in comp.lang.c++ (which
became separated from this one) in which this issue arose. It's not a
problem because the conversion can be called explicitly after the compiler
reports an error (even if Y were made a Q with public inheritance). As I
point out in the thread, the ellipses is the only conversion a user-defined
conversion will beat, a low level conversion which should always lose and
the rest should win over conversions or cause a trivially fixable ambiguity
errors. Overloaded functions pose the only real problem here, calling for
standards.
>
> Bob> I'm already aware of the ambiguity growth vs. expectancy vs. generality issues;
> Bob> again, I'd just like to know the official story.
>
>The article was from someone in the ANSI/ISO group.
Great - this is what I was looking for.
Thanks and Best Regards,
bob
rjh@geodesic.com
P.S. Is this a moderated newsgroup? If not or if response times are very
fast, I'll try to combine this thread with the one in comp.lang.c++.
Author: Bob Hathaway <rjh@geodesic.com>
Date: 1995/03/31 Raw View
class A{};
class Q{};
class X: public Q{
public:
X(const A&);
};
void fn(Q&);
int main(){
A a;
fn(a); // Compilers report an error.
return 0;
}
In the call to fn above, shouldn't derived classes of Q be checked for
conversion by constructor, inversely symmetrical with base classes of A
being checked for conversion functions? I don't see the non-inheritability
of constructors being relevant here (due to direction). And shouldn't the
resultant type of either conversion be a (non-proper) derived class of the
formal, Q? This conversion sequence would be 1 standard conversion and 1
user-defined conversion (or vice-versa), which doesn't violate any rule I'm
aware of.
Our current stock of C++ compiler's don't think so, but what is the standard?
I'm already aware of the ambiguity growth vs. expectancy vs. generality issues;
again, I'd just like to know the official story.
Thank you in advance for any responses (directed to me or this newsgroup).
Best Regards,
bob
rjh@geodesic.com