Topic: Suggestion: Compile-time "typeof
Author: brangdon@cix.co.uk (Dave Harris)
Date: Mon, 30 May 2005 05:41:09 GMT Raw View
house@usq.edu.au (Ron House) wrote (abridged):
> >> It doesn't have multiple inheritance. I don't see why this matters.
> >
> > Samee Zahur has covered this.
In the article dated Mon, 23 May 2005 05:41:17 CST.
> He covered it, but mistakenly, as I explain in my reply to him.
I can't see a reply to the article in google groups.
> The kind of scenario I am thinking of is:
>
> typeof(arg)* f(Base*arg) {
> ... // arg might be made to point elsewhere
> return arg; // No guarantee that *arg is a nonstrict subtype
> // of the original *arg
> }
>
> As far as I can see, this is only typesafe under my proposal if one
> writes:
>
> typeof(arg)* f(Base*const arg) {
It's safe without const if there are no assignments to arg, or if arg is
only assigned with values of the appropriate type. For example:
typeof(this) *Base::clone();
typeof(arg) *f( Base *arg ) {
arg = arg->clone();
return arg;
}
should be type-safe.
> Under my proposal, returning new Base will statically fail to typecheck
> against typeof(this) inside the clone function itself, because we have
> no guarantee that this points to a Base.
It is guaranteed if the clone function is called dynamically, and the
most-derived class has the appropriate override.
> An extra rule for virtuals guaranteeing an overriding clone function
> wouldn't make this safe in any case, because inside any method of
> Derived, we could still call Base::clone specifically, and it would
> not return the promised typeof(this) object.
Only virtual (ie non-qualified) calls would be accepted by the
type-checker. It doesn't matter if they are within a member of Derived or
not:
Derived *p = new Derived;
Derived *p2 = p->Base::clone();
would also need to be rejected.
-- Dave Harris, Nottingham, UK.
---
[ 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: house@usq.edu.au (Ron House)
Date: Tue, 31 May 2005 16:54:17 GMT Raw View
Samee Zahur wrote:
>>I really think you are missing something in your understanding
>>somewhere. At present, when the compiler sees "this", it does *not*
>
> know
>
>>that it will point to a base object at runtime.
>
>
> Actually, it does - well kinda. It could point to a base class object,
> or a base class subobject in an object of decendant class.
Well every inherited class object contains a base object within it.
That's obvious. But you and I both know that I was saying that, for example:
Base *p;
if (...) { p = new Base; } else { p = new Derived; }
// At this point the compiler doesn't know whether
// *p is a Base or a Derived object.
> For example:
>
> struct base1{int a;};
> struct base2{int b;};
> struct derived:base1,base2{int c;};
>
> derived obj;
>
> Now, &obj is of type derived*. To convert this pointer to base1* or
> base2*, the compiler must add a certain constant offset to the original
> pointer. To convert them back to derived*, it must subtract it back.
> The point is, to convert a base pointer to a derived pointer, the
> compiler must know what the derived object is.
True but irrelevant.
> However, in the function you stated:
>
> typeof(arg&) add(arg& a,int x)
> {
> //...
> return a; //Or any other reference of type arg
> }
This example should read:
typeof(a&) add(arg& a,int x)
.
> The function body must be able to convert a from arg& to typeof(arg&).
> To do this it must know the "conversion offset" or so to speak. That
> offset, however, changes from call to call. If two classes were derived
> from arg: d1 and d2, then we have two possibly different constants for
> converting d1* to arg* and for d2* to arg*.
That's not how I propose that this feature should work. As I said
before, the only difference between this function and the currently
possible version that would read:
arg& add(arg& a, int x) ...
is that there is a stronger type check on the result expression.
> So before the compiler can return any reference of type arg&, it must
> know
> typeof(arg&) with which the function will be called - it must know the
> conversion constant corresponding to the actual type of the arguement
> used - an information the function never gets. This is an information
> that will have to be generated separately for every call - and then
> this information will somehow have to be passed into the function.
That might be how your mechanism would have to do it, but mine doesn't.
If you want to find a problem with my proposal, you have to tell me what
mine would need, not what yours needs.
> But yes, where there is a will there is a way. A feature like this
> might as well mandate that any call to a function using typeof to
> require the *caller* and not the *callee* to take care of the
> appropriate conversion. Since the caller already has enough
> information, it can take appropriate measures here. That is, it would
> require the last part of the function to be actually coded on the
> caller's side - outside the function.
> To me, this is getting complicated :)
It already is complicated. You have chosen the complicated multiple
inheritance case to tell me that your head swims. But if you tried
writing a MI case using existing C++ the same complexities would be
there - because they come from MI, not from my proposal.
With single inheritance, the one machine bit pattern will work as a
pointer to the Base and to any Derived object. "Conversion" involves
doing nothing! So my proposal involves nothing more than giving the
compiler at point of call more information so it can allow more legal
programs, and perhaps optimise better. If you involve downcasting, then
(as it stands!) the compiler has to write code that tests if downcasting
is possible, and then do it. The "do it" step is only needed with MI.
Under my proposal, the compiler will know that downcasting is legal,
skip inserting the runtime check, and just write the "do it" code. I
just fail to see how giving the compiler better information and allowing
it to write simpler yet safe code is "getting complicated". "Clearing
the fog" is the metaphor that comes to my mind.
--
Ron House house@usq.edu.au
http://www.sci.usq.edu.au/staff/house
---
[ 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: house@usq.edu.au (Ron House)
Date: Wed, 1 Jun 2005 15:06:41 GMT Raw View
Dave Harris wrote:
> house@usq.edu.au (Ron House) wrote (abridged):
>
>>>>It doesn't have multiple inheritance. I don't see why this matters.
>>>
>>>Samee Zahur has covered this.
>
>
> In the article dated Mon, 23 May 2005 05:41:17 CST.
>
>
>
>>He covered it, but mistakenly, as I explain in my reply to him.
>
>
> I can't see a reply to the article in google groups.
A copy is appended at the end of this post.
>>The kind of scenario I am thinking of is:
>>
>>typeof(arg)* f(Base*arg) {
>> ... // arg might be made to point elsewhere
>> return arg; // No guarantee that *arg is a nonstrict subtype
>> // of the original *arg
>>}
>>
>>As far as I can see, this is only typesafe under my proposal if one
>>writes:
>>
>>typeof(arg)* f(Base*const arg) {
>
>
> It's safe without const if there are no assignments to arg, or if arg is
> only assigned with values of the appropriate type. For example:
>
> typeof(this) *Base::clone();
>
> typeof(arg) *f( Base *arg ) {
> arg = arg->clone();
> return arg;
> }
>
> should be type-safe.
It would be if one were checking the statements within the method. In
this case one could do that, but my proposal was for a less energetic
check by the compiler. The compiler checks the return expression against
a stricter test than it would if the return type were Base. That test
depends only on the declared types of any components of the return
expression, and not upon their history of usage within the method. Yes,
some things that can be seen to be correct will still be rejected, but
the situation is far better than it is in the current standard.
>>Under my proposal, returning new Base will statically fail to typecheck
>>against typeof(this) inside the clone function itself, because we have
>>no guarantee that this points to a Base.
>
> It is guaranteed if the clone function is called dynamically, and the
> most-derived class has the appropriate override.
But I don't want to go there. If that could also be the basis of a sound
proposal, then please make it; but it isn't my proposal.
>
>>An extra rule for virtuals guaranteeing an overriding clone function
>>wouldn't make this safe in any case, because inside any method of
>>Derived, we could still call Base::clone specifically, and it would
>>not return the promised typeof(this) object.
>
>
> Only virtual (ie non-qualified) calls would be accepted by the
> type-checker. It doesn't matter if they are within a member of Derived or
> not:
>
> Derived *p = new Derived;
> Derived *p2 = p->Base::clone();
>
> would also need to be rejected.
Not at all. The first is a perfectly legal C++ assignment that doesn't
use my proposal, and I am not asserting that any existing features
change meaning. the second (given your definition of clone above)
guarantees that clone returns something that is a non-strict subtype of
the type of its owner. Its owner is pointed to by a Derived pointer, and
so cannot be above Derived in the type hierarchy. That satisfies the
requirements for assignment to Derived *p2. I suspect you are thinking
that I am proposing some sort of check of the logic of the method. I am
not, my proposal is 100% static check against declared types.
--
Ron House house@usq.edu.au
http://www.sci.usq.edu.au/staff/house
------------------------------------------------------------------
Samee Zahur wrote:
>> I really think you are missing something in your understanding
>> somewhere. At present, when the compiler sees "this", it does *not*
>
>
> know
>
>> that it will point to a base object at runtime.
>
>
>
> Actually, it does - well kinda. It could point to a base class object,
> or a base class subobject in an object of decendant class.
Well every inherited class object contains a base object within it.
That's obvious. But you and I both know that I was saying that, for example:
Base *p;
if (...) { p = new Base; } else { p = new Derived; }
// At this point the compiler doesn't know whether
// *p is a Base or a Derived object.
> For example:
>
> struct base1{int a;};
> struct base2{int b;};
> struct derived:base1,base2{int c;};
>
> derived obj;
>
> Now, &obj is of type derived*. To convert this pointer to base1* or
> base2*, the compiler must add a certain constant offset to the original
> pointer. To convert them back to derived*, it must subtract it back.
> The point is, to convert a base pointer to a derived pointer, the
> compiler must know what the derived object is.
True but irrelevant.
> However, in the function you stated:
>
> typeof(arg&) add(arg& a,int x)
> {
> //...
> return a; //Or any other reference of type arg
> }
This example should read:
typeof(a&) add(arg& a,int x)
.
> The function body must be able to convert a from arg& to typeof(arg&).
> To do this it must know the "conversion offset" or so to speak. That
> offset, however, changes from call to call. If two classes were derived
> from arg: d1 and d2, then we have two possibly different constants for
> converting d1* to arg* and for d2* to arg*.
That's not how I propose that this feature should work. As I said
before, the only difference between this function and the currently
possible version that would read:
arg& add(arg& a, int x) ...
is that there is a stronger type check on the result expression.
> So before the compiler can return any reference of type arg&, it must
> know
> typeof(arg&) with which the function will be called - it must know the
> conversion constant corresponding to the actual type of the arguement
> used - an information the function never gets. This is an information
> that will have to be generated separately for every call - and then
> this information will somehow have to be passed into the function.
That might be how your mechanism would have to do it, but mine doesn't.
If you want to find a problem with my proposal, you have to tell me what
mine would need, not what yours needs.
> But yes, where there is a will there is a way. A feature like this
> might as well mandate that any call to a function using typeof to
> require the *caller* and not the *callee* to take care of the
> appropriate conversion. Since the caller already has enough
> information, it can take appropriate measures here. That is, it would
> require the last part of the function to be actually coded on the
> caller's side - outside the function.
> To me, this is getting complicated :)
It already is complicated. You have chosen the complicated multiple
inheritance case to tell me that your head swims. But if you tried
writing a MI case using existing C++ the same complexities would be
there - because they come from MI, not from my proposal.
With single inheritance, the one machine bit pattern will work as a
pointer to the Base and to any Derived object. "Conversion" involves
doing nothing! So my proposal involves nothing more than giving the
compiler at point of call more information so it can allow more legal
programs, and perhaps optimise better. If you involve downcasting, then
(as it stands!) the compiler has to write code that tests if downcasting
is possible, and then do it. The "do it" step is only needed with MI.
Under my proposal, the compiler will know that downcasting is legal,
skip inserting the runtime check, and just write the "do it" code. I
just fail to see how giving the compiler better information and allowing
it to write simpler yet safe code is "getting complicated". "Clearing
the fog" is the metaphor that comes to my mind.
---
[ 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: house@usq.edu.au (Ron House)
Date: Mon, 23 May 2005 10:41:59 GMT Raw View
Dave Harris wrote:
> house@usq.edu.au (Ron House) wrote (abridged):
>
>>Of course it is impossible for me to prove a negative. If C++ has a
>>feature that Cool does not, which somehow invalidates this analysis,
>>you tell me what it is.
>
>
> The only articles on Cool I could find were in postscript, and I don't
> have a postscript browser on this machine. Does Cool support repeated
> inheritance? Does it provide a clone() method which is guaranteed to
> return the same (dynamic) type as its argument?
It doesn't have multiple inheritance. I don't see why this matters. The
proposal is for a tighter type check in one place to allow the compiler
to make more assumptions in another. All the assumptions it will make
are guaranteed to be true.
> Your original description implied the function could return either the
> parameter object, or another object with the same type. It would be
> convenient if clone were supported:
>
> typeof(arg1) add( Arg *arg1, Arg *arg2 ) {
> Arg *result = arg1->clone();
> *result += *arg2;
> return result;
> }
>
> which would be statically typesafe if clone is declared like:
>
> typeof(this) Arg::clone();
That's right. With pointers there will be some consts needed in the
declarations to stop one complying pointer being assigned a
non-complying target dynamically (that can't happen with references). It
is easy to imagine features that would amplify the usefulness of the
proposal.
> And it is also easy for the compiler to check that a given implementation
> of clone() returns a suitable type for its class, even if it has "return
> new Arg(*this);". The problem is that a suitable type for a base class may
> not be suitable for a derived class.
>
> I think it suffices to have a rule which says virtual typeof(this)
> functions must be overridden in the most derived class; and non-virtual
> ones must return either this or an object known to be typeof(this) (ie one
> got from another typeof(arg) function).
I don't see the reason why virtual fns need extra rules. Can you show an
example?
> Given that, then we can type-check typeof routines which return different
> objects, so we can't use the trick of returning void and reusing the
> actual parameter instead. So we have a function which returns a base class
> and the compiler needs to downcast that to the type of the argument. And
> it seems to me that the downcast can be ambiguous in the case of repeated
> inheritance.
I'm not clear what kind of scenario you are worried about. A short
example would clarify.
> Another issue is that we really want clone() to be callable on const
> objects but return a non-const result. So we need some mechanism to remove
> const from the typeof(this).
Good point.
> We probably want to support smart pointers as
> well. A smart pointer to Derived typically does not inherit from a smart
> pointer to Base, so we probably need to replace the notion of inheritance
> with some notion of "is convertible to".
I'll have to think harder about that one. The idea is to be able to tell
the compiler some extra info so it can allow more legal programs. If
some legal programs involving smart pointers are being rejected, what
wouls one look like? Perhaps the solution would be to apply the proposed
extension on a smaller scale inside the smart pointer somewhere.
> So there are complications. I don't know how Cool deals with them. I do
> know many toy languages don't bother with repeated inheritance, and they
> provide a universe root class which provides a system-defined clone()
> method, and they don't have const, and they don't have user-defined smart
> pointers. (For example, Java is a toy language by this criteria.)
> Implementing your new feature would be easier in such a language than in
> C++.
Indeed. But the real question is, do these differences actually make a
difference? I don't think clone does, and I can't see that repeated
inheritance does, although I am not quite sure about that one.
--
Ron House house@usq.edu.au
http://www.sci.usq.edu.au/staff/house
---
[ 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: "Samee Zahur" <samee.zahur@gmail.com>
Date: Mon, 23 May 2005 05:41:17 CST Raw View
> I really think you are missing something in your understanding
> somewhere. At present, when the compiler sees "this", it does *not*
know
> that it will point to a base object at runtime.
Actually, it does - well kinda. It could point to a base class object,
or a base class subobject in an object of decendant class. For example:
struct base1{int a;};
struct base2{int b;};
struct derived:base1,base2{int c;};
derived obj;
Now, &obj is of type derived*. To convert this pointer to base1* or
base2*, the compiler must add a certain constant offset to the original
pointer. To convert them back to derived*, it must subtract it back.
The point is, to convert a base pointer to a derived pointer, the
compiler must know what the derived object is.
However, in the function you stated:
typeof(arg&) add(arg& a,int x)
{
//...
return a; //Or any other reference of type arg
}
The function body must be able to convert a from arg& to typeof(arg&).
To do this it must know the "conversion offset" or so to speak. That
offset, however, changes from call to call. If two classes were derived
from arg: d1 and d2, then we have two possibly different constants for
converting d1* to arg* and for d2* to arg*.
So before the compiler can return any reference of type arg&, it must
know
typeof(arg&) with which the function will be called - it must know the
conversion constant corresponding to the actual type of the arguement
used - an information the function never gets. This is an information
that will have to be generated separately for every call - and then
this information will somehow have to be passed into the function.
But yes, where there is a will there is a way. A feature like this
might as well mandate that any call to a function using typeof to
require the *caller* and not the *callee* to take care of the
appropriate conversion. Since the caller already has enough
information, it can take appropriate measures here. That is, it would
require the last part of the function to be actually coded on the
caller's side - outside the function.
To me, this is getting complicated :)
Samee
---
[ 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: brangdon@cix.co.uk (Dave Harris)
Date: Mon, 23 May 2005 20:21:49 GMT Raw View
house@usq.edu.au (Ron House) wrote (abridged):
> It doesn't have multiple inheritance. I don't see why this matters. The
> proposal is for a tighter type check in one place to allow the compiler
> to make more assumptions in another. All the assumptions it will make
> are guaranteed to be true.
Samee Zahur has covered this.
> > typeof(arg1) add( Arg *arg1, Arg *arg2 ) {
> > Arg *result = arg1->clone();
> > *result += *arg2;
> > return result;
> > }
> >
> > which would be statically typesafe if clone is declared like:
> >
> > typeof(this) Arg::clone();
>
> That's right. With pointers there will be some consts needed in the
> declarations to stop one complying pointer being assigned a
> non-complying target dynamically (that can't happen with references).
I don't think assignments add any new difficulties over initialisation, so
const isn't needed. For example:
Arg *result = arg1->clone();
if (condition())
result = new Arg;
is no harder to check than:
Arg *result = condition() ? arg1->clone() : new Arg;
and this latter is not made easier by using const. Once we allow a
different object to be returned, we have to deal with this stuff.
> I don't see the reason why virtual fns need extra rules. Can you show
> an example?
Consider a clone function:
typeof(this) Base::clone() {
return new Base( *this );
}
When called on an object whose most-derived type is Base, the type
returned is the same as the type of this, so it is correct. However, if
class Derived inherits from Base and doesn't override clone(), the
inherited function is now returning the wrong type. It is returning a Base
when it is effectively declared to return a Derived.
-- Dave Harris, Nottingham, UK.
---
[ 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: brangdon@cix.co.uk (Dave Harris)
Date: Mon, 23 May 2005 20:21:14 GMT Raw View
jdennett@acm.org (James Dennett) wrote (abridged):
> We can certainly return another object of the same dynamic type
> as *this in some cases, without needing to create one dynamically:
>
> if (typeid(out) == typeid(typeX)) {
> static typeX x;
> return x;
> } else {
> return *this;
> }
Would you say this was statically type-checkable? It seems to require
flow-analysis and understanding of what typeid means. It's borderline; I'd
hesitate before requiring that C++ compilers type-check it.
-- Dave Harris, Nottingham, UK.
---
[ 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: jdennett@acm.org (James Dennett)
Date: Tue, 24 May 2005 05:13:58 GMT Raw View
Dave Harris wrote:
> jdennett@acm.org (James Dennett) wrote (abridged):
>
>>We can certainly return another object of the same dynamic type
>>as *this in some cases, without needing to create one dynamically:
>>
>>if (typeid(out) == typeid(typeX)) {
>> static typeX x;
>> return x;
>>} else {
>> return *this;
>>}
>
>
> Would you say this was statically type-checkable? It seems to require
> flow-analysis and understanding of what typeid means.
Agreed; it's non-trivial to type check this statically.
> It's borderline; I'd
> hesitate before requiring that C++ compilers type-check it.
I'd really not want to do so; I showed it only to show one
of the complications inherent in the typeof(*this) return
proposal under discussion.
-- James
---
[ 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: house@usq.edu.au (Ron House)
Date: Tue, 24 May 2005 17:56:22 GMT Raw View
Dave Harris wrote:
> jdennett@acm.org (James Dennett) wrote (abridged):
>
>>We can certainly return another object of the same dynamic type
>>as *this in some cases, without needing to create one dynamically:
>>
>>if (typeid(out) == typeid(typeX)) {
>> static typeX x;
>> return x;
>>} else {
>> return *this;
>>}
>
>
> Would you say this was statically type-checkable? It seems to require
> flow-analysis and understanding of what typeid means. It's borderline; I'd
> hesitate before requiring that C++ compilers type-check it.
I wouldn't want the compiler passing this example. The proposal is
supposed to be for modified static checks. You correctly point out that
we have to follow flow of control to understand that the code will work.
The compiler should reject the line "return x" because, looking at that
line in a vacuum, typeX might not be a nonstrict subtype of typeid(out).
--
Ron House house@usq.edu.au
http://www.sci.usq.edu.au/staff/house
---
[ 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: house@usq.edu.au (Ron House)
Date: Tue, 24 May 2005 17:55:15 GMT Raw View
Dave Harris wrote:
> house@usq.edu.au (Ron House) wrote (abridged):
>
>>It doesn't have multiple inheritance. I don't see why this matters. The
>>proposal is for a tighter type check in one place to allow the compiler
>>to make more assumptions in another. All the assumptions it will make
>>are guaranteed to be true.
>
>
> Samee Zahur has covered this.
He covered it, but mistakenly, as I explain in my reply to him.
>>> typeof(arg1) add( Arg *arg1, Arg *arg2 ) {
>>> Arg *result = arg1->clone();
>>> *result += *arg2;
>>> return result;
>>> }
>>>
>>>which would be statically typesafe if clone is declared like:
>>>
>>> typeof(this) Arg::clone();
>>
>>That's right. With pointers there will be some consts needed in the
>>declarations to stop one complying pointer being assigned a
>>non-complying target dynamically (that can't happen with references).
>
>
> I don't think assignments add any new difficulties over initialisation, so
> const isn't needed. For example:
>
> Arg *result = arg1->clone();
> if (condition())
> result = new Arg;
>
> is no harder to check than:
>
> Arg *result = condition() ? arg1->clone() : new Arg;
>
> and this latter is not made easier by using const. Once we allow a
> different object to be returned, we have to deal with this stuff.
The kind of scenario I am thinking of is:
typeof(arg)* f(Base*arg) {
... // arg might be made to point elsewhere
return arg; // No guarantee that *arg is a nonstrict subtype
// of the original *arg
}
As far as I can see, this is only typesafe under my proposal if one writes:
typeof(arg)* f(Base*const arg) {
>>I don't see the reason why virtual fns need extra rules. Can you show
>>an example?
>
>
> Consider a clone function:
>
> typeof(this) Base::clone() {
> return new Base( *this );
> }
>
> When called on an object whose most-derived type is Base, the type
> returned is the same as the type of this, so it is correct. However, if
> class Derived inherits from Base and doesn't override clone(), the
> inherited function is now returning the wrong type. It is returning a Base
> when it is effectively declared to return a Derived.
Under my proposal, returning new Base will statically fail to typecheck
against typeof(this) inside the clone function itself, because we have
no guarantee that this points to a Base. An extra rule for virtuals
guaranteeing an overriding clone function wouldn't make this safe in any
case, because inside any method of Derived, we could still call
Base::clone specifically, and it would not return the promised
typeof(this) object.
--
Ron House house@usq.edu.au
http://www.sci.usq.edu.au/staff/house
---
[ 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: max.teneyck.woodbury@verizon.net ("Max T. Woodbury")
Date: Wed, 11 May 2005 01:34:16 GMT Raw View
Ron House wrote:
> It often happens that one want to propagate an argument or an object
> type as the result of a function. When the arg is a ref or ptr, the
> actual arg might not be the declared type, but a descendant. It would be
> very useful to be able to propagate that information into the result
> type. For example,
>
> typeof(out) operator<<(ostream& out, const Person& p) {
> ... output the Person data ...
> return out;
> }
>
> Now, if the << operator is used as part of a larger expression, the
> compiler might be able to deduce stronger limits on the type of the
> entire expression than it can if the result type is merely ostream.
>
> I ran into this one while writing a parent class with methods that were
> designed to be 'strung together', each returning a ref to the owning
> object, *this. The classes would string together nicely, but when used
> in a descendant, the compiler thought the type was the ancestor, so I
> had to write inline 'shell' functions in every descendant to merely call
> the ancestor but give it the more descriptive type. It would have been
> great to be able to write something like:
>
> typeof(*this)& mymethod() { ... return *this; }
>
> Of course, I don't think it is beyond credibility that I have entirely
> overlooked some way that already exists to accomplish this...
>
GRIN! You really should search the archives. I proposed almost exactly
this quite some time ago only to discover that the GNU compiler already
had something like it. Further, you can apparently get this information
with an appropriate specified template class.
max@mtew.isa-geek.net
---
[ 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: jdennett@acm.org (James Dennett)
Date: Wed, 11 May 2005 20:58:23 GMT Raw View
Ron House wrote:
> It often happens that one want to propagate an argument or an object
> type as the result of a function. When the arg is a ref or ptr, the
> actual arg might not be the declared type, but a descendant. It would be
> very useful to be able to propagate that information into the result
> type. For example,
>
> typeof(out) operator<<(ostream& out, const Person& p) {
> ... output the Person data ...
> return out;
> }
>
> Now, if the << operator is used as part of a larger expression, the
> compiler might be able to deduce stronger limits on the type of the
> entire expression than it can if the result type is merely ostream.
>
> I ran into this one while writing a parent class with methods that were
> designed to be 'strung together', each returning a ref to the owning
> object, *this. The classes would string together nicely, but when used
> in a descendant, the compiler thought the type was the ancestor, so I
> had to write inline 'shell' functions in every descendant to merely call
> the ancestor but give it the more descriptive type. It would have been
> great to be able to write something like:
>
> typeof(*this)& mymethod() { ... return *this; }
>
> Of course, I don't think it is beyond credibility that I have entirely
> overlooked some way that already exists to accomplish this...
>
typeof has been implemented by some compilers; the proposal under
consideration by the committee for the next C++ standard uses the
keyword "decltype" for something along similar lines.
-- James
---
[ 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: d.frey@gmx.de (Daniel Frey)
Date: Wed, 11 May 2005 20:58:20 GMT Raw View
Ron House wrote:
> type. For example,
>
> typeof(out) operator<<(ostream& out, const Person& p) {
> ... output the Person data ...
> return out;
> }
>
> Now, if the << operator is used as part of a larger expression, the
> compiler might be able to deduce stronger limits on the type of the
> entire expression than it can if the result type is merely ostream.
>
> I ran into this one while writing a parent class with methods that were
> designed to be 'strung together', each returning a ref to the owning
> object, *this. The classes would string together nicely, but when used
> in a descendant, the compiler thought the type was the ancestor, so I
> had to write inline 'shell' functions in every descendant to merely call
> the ancestor but give it the more descriptive type. It would have been
> great to be able to write something like:
>
> typeof(*this)& mymethod() { ... return *this; }
>
> Of course, I don't think it is beyond credibility that I have entirely
> overlooked some way that already exists to accomplish this...
You mean like:
template< typename T >
T& operator<<( T& out, const Person& p ) { ...; return out; }
Obviously this doesn't solve the general case you addressed, but I guess
your example is out ;)
Regards, Daniel
---
[ 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: AlbertoBarbati@libero.it (Alberto Barbati)
Date: Wed, 11 May 2005 20:59:56 GMT Raw View
Max T. Woodbury wrote:
> Ron House wrote:
>
> GRIN! You really should search the archives. I proposed almost exactly
> this quite some time ago only to discover that the GNU compiler already
> had something like it. Further, you can apparently get this information
> with an appropriate specified template class.
>
You really should search the archives, too. There is an ongoing formal
proposal of a typeof-like core language feature, called decltype. You
can have a read about it here:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1705.pdf
The paper also contains the rationale why the proposed name is not
typeof and much more (a new use of the auto keyword, a new syntax for
specifying function return values, etc.)
Alberto
---
[ 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: "msalters" <Michiel.Salters@logicacmg.com>
Date: Wed, 11 May 2005 20:33:21 CST Raw View
Ron House schreef:
> It often happens that one want to propagate an argument or an object
> type as the result of a function. When the arg is a ref or ptr, the
> actual arg might not be the declared type, but a descendant. It would
be
> very useful to be able to propagate that information into the result
> type. For example,
>
> typeof(out) operator<<(ostream& out, const Person& p) {
> ... output the Person data ...
> return out;
> }
That is of course impossible. The returned object has a static and
dynamic type. Clearly, ostream& is the static type, and the dynamic
type is whatever is passed in at runtime. You cannot make the static
type identical to the dynamic type, because (a) there can be multiple
dynamic types, for each call to operator<< while there's only one
static
type and (b) static types are determined before dynamic types are even
known.
HTH,
Michiel Salters
---
[ 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: house@usq.edu.au (Ron House)
Date: Thu, 12 May 2005 06:06:43 GMT Raw View
Daniel Frey wrote:
> You mean like:
>
> template< typename T >
> T& operator<<( T& out, const Person& p ) { ...; return out; }
>
> Obviously this doesn't solve the general case you addressed, but I guess
> your example is out ;)
Yes, that one does seem to solve the problem when there is an explicit
argument, although it leaves the method return type out in the cold. And
it sort of rankles to use a template for such a simple requirement.
--
Ron House house@usq.edu.au
http://www.sci.usq.edu.au/staff/house
---
[ 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: house@usq.edu.au (Ron House)
Date: Thu, 12 May 2005 06:06:08 GMT Raw View
Alberto Barbati wrote:
> Max T. Woodbury wrote:
>
>>Ron House wrote:
>>
>>GRIN! You really should search the archives. I proposed almost exactly
>>this quite some time ago only to discover that the GNU compiler already
>>had something like it. Further, you can apparently get this information
>>with an appropriate specified template class.
>>
>
>
> You really should search the archives, too. There is an ongoing formal
> proposal of a typeof-like core language feature, called decltype. You
> can have a read about it here:
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1705.pdf
>
> The paper also contains the rationale why the proposed name is not
> typeof and much more (a new use of the auto keyword, a new syntax for
> specifying function return values, etc.)
Thanks for that ref. Having read it, though, I see they are not actually
solving the problem I am raising. Their proposal is largely syntactic
sugar. Their decltype gives the type of the formal parameter, not the
(compile-time) type of the actual parameter, and is therefore vastly
less useful. What I want is this:
class A {
...
A& mymethod() { ... return *this; }
};
class B: public A {
...
B& bmethod() { ... }
};
B b;
. b.mymethod() <--- at this point the compiler knows the expression
result is a B&, not an A&.
The code as written clearly tells the compiler that mymethod returns an
A&, but in fact static examination of the code promises that mymethod is
actually returning a B& at this point. My suggested rewriting of mymethod:
typeof(*this)& mymethod() { ... return *this; }
ensures that no other returns can be inserted returning supertypes of B,
because, in order to write a return that satisfies typeof(*this)&, it
more or less can't be anything but *this. Any actual typed expression
(such as an A variable) _might_ be a supertype of *this at runtime. So
consider:
A& mymethod() {
if (...) return somethingoftypeA; else return *this;
}
The above is syntactically correct, and we have no guarantee that
mymethod will, for a B object, return a B. The compiler must treat the
result as of type A, and the following will be illegal:
b.mymethod().bmethod();
However, if we could write:
typeof(*this)& mymethod() {
if (...) return somethingoftypeA; else return *this;
}
then the compiler would give a syntax error because it cannot guarantee
that somethingoftypeA will indeed have a type compatible with *this at
runtime (because the method might be executed from a descendant).
However, the simpler version:
typeof(*this)& mymethod() { ... return *this; }
will be correct, and can be guaranteed at compile time to actually
return something equal or a subtype of its owning object at runtime.
Then the call:
b.mymethod().bmethod();
will be seen to be sane and will compile correctly.
Aikens designed a simple language called Cool for his compiler design
class, and he convinced me with it that he is a genius! Cool does all
this right, despite being a 'toy' language. There is no reason at all
C++ should get it wrong. (See http://www.cs.berkeley.edu/~aiken/cool)
--
Ron House house@usq.edu.au
http://www.sci.usq.edu.au/staff/house
---
[ 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: hyrosen@mail.com (Hyman Rosen)
Date: Mon, 16 May 2005 16:19:41 GMT Raw View
Ron House wrote:
> typeof(*this)& mymethod() {
> if (...) return somethingoftypeA; else return *this;
> }
I doubt the utility of returning a reference to a different object
of the same type as the caller, so I would refine this proposal
to simply allow a method to be declared of type 'this', and have
such a method implicitly return *this without needing it to be
explicitly specified. It's the perfect return type for assignment
operators, as well as the stream pass-throughs.
struct mystream : ostream {
template <typename T>
this operator<<(T v) { (ostream &)*this << v; }
};
struct point {
double x, y, z;
this operator=(const point &o) { x = o.x; y = o.y; z = o.z; }
};
And the static type of a 'this' method invocation will of course be
the static type of the class to which the initial invoking pointer,
reference, or actual object refers.
---
[ 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: house@usq.edu.au (Ron House)
Date: Mon, 16 May 2005 16:19:12 GMT Raw View
msalters wrote:
> Ron House schreef:
>
>>It often happens that one want to propagate an argument or an object
>>type as the result of a function. When the arg is a ref or ptr, the
>>actual arg might not be the declared type, but a descendant. It would
>
> be
>
>>very useful to be able to propagate that information into the result
>>type. For example,
>>
>>typeof(out) operator<<(ostream& out, const Person& p) {
>> ... output the Person data ...
>> return out;
>>}
>
>
> That is of course impossible. The returned object has a static and
> dynamic type. Clearly, ostream& is the static type, and the dynamic
> type is whatever is passed in at runtime.
I never mentioned the dynamic type, I mentioned the type of the actual
parameter, meaning the declared type as known to the compiler.
I think you misunderstand the requirement. I am not asking that the
compiler use magic to know what the dynamic type will be at runtime. But
the compiler very well will know the declared type of the actual
parameter at compile time, and that (in order to allow at runtime
descendants to be passed) might be a descendant of the type of the
formal parameter. It will know statically that the method returns that
descendant type. But C++ doesn't allow me to specify this to the
compiler, entirely due a language shortcoming, nothing to do with
runtime types at all. And the decltype suggestion does NOT meet that need.
--
Ron House house@usq.edu.au
http://www.sci.usq.edu.au/staff/house
---
[ 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: house@usq.edu.au (Ron House)
Date: Mon, 16 May 2005 16:19:02 GMT Raw View
James Dennett wrote:
> typeof has been implemented by some compilers; the proposal under
> consideration by the committee for the next C++ standard uses the
> keyword "decltype" for something along similar lines.
Unfortunately the similar lines are not similar enough to solve the
major problem. See my other post today.
--
Ron House house@usq.edu.au
http://www.sci.usq.edu.au/staff/house
---
[ 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: AlbertoBarbati@libero.it (Alberto Barbati)
Date: Mon, 16 May 2005 16:21:56 GMT Raw View
Ron House wrote:
> Alberto Barbati wrote:
>
>> You really should search the archives, too. There is an ongoing formal
>> proposal of a typeof-like core language feature, called decltype. You
>> can have a read about it here:
>> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1705.pdf
>>
>> The paper also contains the rationale why the proposed name is not
>> typeof and much more (a new use of the auto keyword, a new syntax for
>> specifying function return values, etc.)
>
> Thanks for that ref. Having read it, though, I see they are not actually
> solving the problem I am raising. Their proposal is largely syntactic
> sugar.
I'm not sure the authors of that proposal are very happy the hear
that... ;-)
> sugar. Their decltype gives the type of the formal parameter, not the
> (compile-time) type of the actual parameter, and is therefore vastly
> less useful. What I want is this:
>
> class A {
> ...
> A& mymethod() { ... return *this; }
> };
>
> class B: public A {
> ...
> B& bmethod() { ... }
> };
>
> B b;
> . b.mymethod() <--- at this point the compiler knows the expression
> result is a B&, not an A&.
Hmmmm... So you are telling me that you want the *same* function
A::mymethod to return two different types and the compiler should choose
which type according to type of one of the functions parameters (the
implicit "this" parameter, in this case)?
I'm not saying that it couldn't be done, but:
1) if it's limited to typeof(*this), then it's just some kind of limited
static covariance. It might even be easy to implement, although
requiring a new keyword for this feature looks a bit too much to me.
Frankly I don't see a case where this feature could really be useful.
Could you please enlighten me by posting a *real-life* use case where
this idiom could effectively help?
Moreover, consider this:
class A {
...
A& mymethod() { ... return *this; }
};
template <class T>
T& mymethod(T& x) { x.mymethod(); return x; }
and just use mymethod(b) instead of b.mymethod().
2) if this feature is taken in full generality (any expression in typeof
depending on any parameter) I find it hard to believe that such thing
can be implemented without troubles...
>
> Aikens designed a simple language called Cool for his compiler design
> class, and he convinced me with it that he is a genius! Cool does all
> this right, despite being a 'toy' language. There is no reason at all
> C++ should get it wrong. (See http://www.cs.berkeley.edu/~aiken/cool)
>
I have no reason to doubt about the intelligence of this person, but the
fact that you don't see a reason why C++ does not have such a feature
does not imply that they don't exist, neither that C++ is "wrong" about
it. You have to be more convincing than that.
Alberto
---
[ 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: brangdon@cix.co.uk (Dave Harris)
Date: Mon, 16 May 2005 22:04:24 GMT Raw View
hyrosen@mail.com (Hyman Rosen) wrote (abridged):
> > typeof(*this)& mymethod() {
> > if (...) return somethingoftypeA; else return *this;
> > }
>
> I doubt the utility of returning a reference to a different object
> of the same type as the caller, so I would refine this proposal
> to simply allow a method to be declared of type 'this', and have
> such a method implicitly return *this without needing it to be
> explicitly specified.
Interesting digression, but I think the original proposal was supposed to
work for non-member functions too. It would be handy if:
typeof( str ) strchr( const char *str, char ch );
char str[10] = "test";
char *s = strchr( str, 's' );
*s = 'x';
compiled. This example doesn't use classes or inheritance at all.
Returning an object of the same type as an argument, but not the same
object, is useful sometimes. For example, the intersection of one
rectangle with another rectangle is always a rectangle. It may or may not
be equal to one or both argument rectangles, and if it is equal, it may or
may not be the same object.
However, if it is a different object, I doubt the compiler could
statically prove it was the same type, in general.
-- Dave Harris, Nottingham, UK.
---
[ 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: Hyman Rosen <hyrosen@mail.com>
Date: Mon, 16 May 2005 17:15:50 CST Raw View
Dave Harris wrote:
> Interesting digression
The sneaky part of my suggestion, if you looked at my
code sample, was that such functions looked very much
like they could be declared as returning void. And they
could be, because all the compiler has to do is at the
call site, make believe that the function has returned
the call object (or in your example, some distinguished
parameter).
---
[ 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: Seungbeom Kim <musiphil@bawi.org>
Date: Mon, 16 May 2005 19:59:38 CST Raw View
Dave Harris wrote:
>
> Interesting digression, but I think the original proposal was supposed to
> work for non-member functions too. It would be handy if:
>
> typeof( str ) strchr( const char *str, char ch );
>
> char str[10] = "test";
> char *s = strchr( str, 's' );
> *s = 'x';
>
> compiled. This example doesn't use classes or inheritance at all.
>
> Returning an object of the same type as an argument, but not the same
> object, is useful sometimes. For example, the intersection of one
> rectangle with another rectangle is always a rectangle. It may or may not
> be equal to one or both argument rectangles, and if it is equal, it may or
> may not be the same object.
Why not use template?
template <typename Char>
Char* strchr(Char* str, char ch);
--
Seungbeom Kim
---
[ 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: Ron House <house@usq.edu.au>
Date: 17 May 2005 05:40:02 GMT Raw View
Alberto Barbati wrote:
> Ron House wrote:
> Hmmmm... So you are telling me that you want the *same* function
> A::mymethod to return two different types and the compiler should choose
> which type according to type of one of the functions parameters (the
> implicit "this" parameter, in this case)?
No, it should return one type, but that type is specified to be the type
of a parameter, not a fixed type. In C++, this is of course only useful
with pointers and references, as they can pass descendants.
> I'm not saying that it couldn't be done, but:
>
> 1) if it's limited to typeof(*this), then it's just some kind of limited
> static covariance. It might even be easy to implement, although
> requiring a new keyword for this feature looks a bit too much to me.
It _is_ easy to implement. Every student in my compiler course
implemented it when developing the compiler for Aiken's toy "Cool"
language - and it is much more useful than one would suspect.
> Frankly I don't see a case where this feature could really be useful.
> Could you please enlighten me by posting a *real-life* use case where
> this idiom could effectively help?
Okay, here's a simple program that currently gives a syntax error, but
which obviously would work if allowed to compile:
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ofstream f("fred.txt");
(f << "hello\n").close();
return 0;
}
To make it work, we would need to define << something like this:
typeof(out)& operator<<(ostream& out, const char* str) {
...
return out;
}
Here, out stands for the type of the actual parameter. When typechecking
within the definition of <<, the compiler will do exactly what it always
has, except that, at the return statement, only out or a provable
descendant of whatever class out has, will be accepted. At the point of
call, instead of using ostream& as the result type of <<, it will use
whatever declared type the expression given for the out parameter has.
What would the compiler have to do, in terms of generating new code?
Absolutely nothing. All that is needed is some way to get past the type
checking.
The actual real-world example I found was when I wrote a package for
generating html. I wanted a method for every attribute a given html tag
had. So, to generate:
<div id="fred" align="left">
I want to write in C++:
cout << div().id("fred").align("left");
The payoff is for the complicated classes like table, so please don't
complain that this would be as easy to write in html.
The trouble is that the id attribute is available for many html
elements, and so would be most logically provided in an ancestor class
from which div is descended. But the align element is specific to div
(and some others). But if (as I had to do at present) I define id as a
method of the ancestor, it must return the ancestor type, and then
adding the align call generates a syntax error. I have to redefine id in
every single class in the system, hundreds of them, doing nothing more
than calling the ancestor id class inside a bit of retyping fluff. Being
able to retype the result would have been a huge win in this case.
> Moreover, consider this:
>
> class A {
> ...
> A& mymethod() { ... return *this; }
> };
>
> template <class T>
> T& mymethod(T& x) { x.mymethod(); return x; }
>
> and just use mymethod(b) instead of b.mymethod().
As the html example above shows, such a change would completely mess up
the nice idiom of tacking on a method call per attribute. A long string
of attributes all nesting the tag name which trundles along at the end
would make the system psychologically unuseable as a neat way to
translate one's knowledge of html into code that writes html. I can
write my calls as they are in a semi-comatose state. I very much doubt I
could do that if I had to use functional notation. And remember, the
ability to do neat notational things with C++, as in the elegant i-o
system style, is central to the philosophy of the language, as opposed
to the "you must do it our way" mindset of most languages.
> 2) if this feature is taken in full generality (any expression in typeof
> depending on any parameter) I find it hard to believe that such thing
> can be implemented without troubles...
In effect the only freedom you get is descendants in references and
pointers. It is easy to do, and I have done it, as I said, in writing
the compiler for Aiken's beautiful toy language, Cool. How embarrassing
that a compiler class toy language can do something so neat and C++ can
not! Aiken's writeup tells you the exact rules that need to be
implemented to make it work. They simply are not hard - dead easy in fact.
>>Aikens designed a simple language called Cool for his compiler design
>>class, and he convinced me with it that he is a genius! Cool does all
>>this right, despite being a 'toy' language. There is no reason at all
>>C++ should get it wrong. (See http://www.cs.berkeley.edu/~aiken/cool)
> I have no reason to doubt about the intelligence of this person, but the
> fact that you don't see a reason why C++ does not have such a feature
> does not imply that they don't exist, neither that C++ is "wrong" about
> it. You have to be more convincing than that.
Of course it is impossible for me to prove a negative. If C++ has a
feature that Cool does not, which somehow invalidates this analysis, you
tell me what it is.
--
Ron House house@usq.edu.au
http://www.sci.usq.edu.au/staff/house
---
[ 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: brangdon@cix.co.uk (Dave Harris)
Date: Wed, 18 May 2005 05:39:59 GMT Raw View
musiphil@bawi.org (Seungbeom Kim) wrote (abridged):
> > typeof( str ) strchr( const char *str, char ch );
>
> Why not use template?
Because templates suck?
Making it a template has several drawbacks. Firstly, it requires us to
expose the implementation. This is bad for dependency management and runs
the risk of accidentally picking up a dependant name at the point of
instantiation. Secondly, it prevents the function from being a virtual
member. Thirdly, it makes the function more generic than we may want.
A thin template wrapper around a worker function would work, but that's
not a whole lot easier than just writing the overload directly.
-- Dave Harris, Nottingham, UK.
---
[ 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: "Samee Zahur" <samee.zahur@gmail.com>
Date: 18 May 2005 05:50:15 GMT Raw View
I sort of caught this thread 'in the middle of the flow', but here's
what
I understand about this proposal:
All the functionality described here is already pretty much there - but
you
have to use templates as many has already noted. But we would like to
avoid
that, right? Now here's the proposal from the point of view of a
compiler:
The compiler sees a code such as
typeof(out)& operator>>(ostream& out,int x)
{
//...
return out;
}
And it should realise that this function could be called with any
parameter with any compile-time type derived from ostream. This in turn
means there's no way the compiler can generate the binary code for the
function right away - that would depend on the type of parameter
actually
supplied. It would have to wait till the function is actually used
somewhere.
At that point (point of instantization) the function code is generated
and
called. But for that to happen successfully, the function definition
(body)
must be available for compilation - it must *not* be compiled
separately or
the compiler would fail to generate the appropriate binary code!
In short, such a function would have *all* the bells and whistles
usually
attached to a template function.
It should be clear from this paragraph that the compiler would have to
treat such a function exactly as a template function ... specifically
in
this case:
template <class T> T& operator>>(T& out, int x)
{
//Might want to check if T is derived from ostream or not
//...
return out;
}
This is exactly the kind of functionality templates were supposed
to provide ... I don't see anything new being done here ... and I don't
see
any problem with using templates here either. But someone else might
help
me there. I'd like it if someone could point out anything new being
done here
except for making a new notation for something old.
Samee
---
[ 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: brangdon@cix.co.uk (Dave Harris)
Date: 19 May 2005 04:10:01 GMT Raw View
samee.zahur@gmail.com (Samee Zahur) wrote (abridged):
> typeof(out)& operator>>(ostream& out,int x)
> {
> //...
> return out;
> }
>
>
> And it should realise that this function could be called with any
> parameter with any compile-time type derived from ostream. This in turn
> means there's no way the compiler can generate the binary code for the
> function right away - that would depend on the type of parameter
> actually supplied.
The body of the function does not depend on the actual parameter type,
only on the declared type. The function could be split into two parts,
like:
ostream &operator>>( ostream &out, int x );
template <typename T>
T &operator>>( T &out, int x ) {
ostream &o1( out );
ostream &o2 = operator>>( o1, x );
return static_cast<T &>( o2 );
}
At least in the absence of virtual inheritance. In practice I believe the
original function-body is only statically type-checkable in isolation if
the return object is identical to the passed one, so we can use Hyman
Rosen's idea:
void __shift_ostream_int( ostream &out, int x );
template <typename T>
T &operator>>( T &out, int x ) {
ostream &o1 = out;
__shift_ostream_int( o1, x );
return out;
}
This avoids the cast and works in all cases.
> [...] I don't see any problem with using templates here either.
One problem with this is that the template is too generic. For example, if
we want two overloads:
typeof(out)& operator>>( ostream1& out, int x);
typeof(out)& operator>>( ostream2& out, int x);
both of them will be trying to create the same template function:
template <typename T> T &operator>>( T &out, int x );
I don't see how the compiler can deal with this. We would need something
like constrained genericity, a way to say a template parameter T can only
match subclasses of ostream1. Maybe this can be done using advanced
techniques such as "substitution failure is not an error", but I doubt the
result will be very pretty.
A second problem is that it requires us to write two functions, the
template function and the underlying normal function. If instead we try to
write it all as a template, then the function can't be virtual and has its
implementation exposed, and all the other template issues.
-- Dave Harris, Nottingham, UK.
---
[ 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: house@usq.edu.au (Ron House)
Date: Thu, 19 May 2005 06:38:19 GMT Raw View
Samee Zahur wrote:
> I sort of caught this thread 'in the middle of the flow', but here's
> what
> I understand about this proposal:
>
> All the functionality described here is already pretty much there - but
> you
> have to use templates as many has already noted. But we would like to
> avoid
> that, right? Now here's the proposal from the point of view of a
> compiler:
>
> The compiler sees a code such as
>
> typeof(out)& operator>>(ostream& out,int x)
> {
> //...
> return out;
> }
>
>
> And it should realise that this function could be called with any
> parameter with any compile-time type derived from ostream.
This is already true in the language as it stands.
> This in turn
> means there's no way the compiler can generate the binary code for the
> function right away - that would depend on the type of parameter
> actually
> supplied. It would have to wait till the function is actually used
> somewhere.
No. There is absolutely no difference in code generated because the
argument can already refer to a descendant. The proposal isn't about the
argument handling at all, it is about what is legal after the word
"return" when sending back the result. Not just any ostream can be
returned, only one that can be proved to be of the type of the actual
parameter or a descendant of it. I think I am right in asserting that in
C++, the only expression that will qualify is the formal parameter
itself ("out" in the above example); but I might have overlooked
something or they might add something in the new standard that
introduces other possibilities. In other words, the proposal effectively
asks for a stricter type check on one particular expression within the
function (the return expression), and doesn't affect anything else in
compiling the function - no type conversions, no runtime checks, no nothing.
> At that point (point of instantization) the function code is generated
> and
> called. But for that to happen successfully, the function definition
> (body)
> must be available for compilation - it must *not* be compiled
> separately or
> the compiler would fail to generate the appropriate binary code!
All this is incorrect for the reason given above. At instantiation,
because of the stricter type check in the function itself, the compiler
knows more about the specific type of the result of the expression, and
so (because it knows more) it can be more aggressive in permitting
further operations. In the example I gave:
ofstream f("fred.txt");
(f << "hello\n").close();
the particular more aggressive thing it can assume is that the result of
f<<"hello" is an ofstream or descendant, not merely an ostream or
descendant. It will be correct in this deduction, and that will permit
more expressive developments of that expression, such as tacking on
".close()" in the above example.
> In short, such a function would have *all* the bells and whistles
> usually
> attached to a template function.
Again, this is incorrect. The proposal is incredibly useful and requires
no runtime checks, no templates or equivalents, and is simplicity itself
to implement in the compiler.
--
Ron House house@usq.edu.au
http://www.sci.usq.edu.au/staff/house
---
[ 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: hyrosen@mail.com (Hyman Rosen)
Date: Thu, 19 May 2005 20:31:25 GMT Raw View
Ron House wrote:
> No, it should return one type, but that type is specified to be the type
> of a parameter, not a fixed type.
Assuming that we only want to return *this or a reference parameter
(rather than creating a new object of the appropriate derived type),
the function itself doesn't have to return anything - the compiler
can just substitute in the parameter/object multiple times.
> ofstream f("fred.txt");
> (f << "hello\n").close();
Would be turned by the compiler into
ofstream f("fred.txt");
f << "hello\n";
f.close();
> cout << div().id("fred").align("left");
Would be turned by the compiler into
Div_t &d = div();
d.id("fred");
d.align("left");
cout << d;
Notice that the id and align functions don't actually need to return
anything at all, because the compiler simply uses an object available
at the call site as the pseudo return value.
So all we need is syntax. For members returning *this, I already
suggested using just 'this' as the return type. For parameters,
how about allowing one of them to be designated as 'return'?
template<typename T>
void operator<<(return mystream &out, const T &value)
{ static_cast<ostream &>(out) << value; }
---
[ 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: "msalters" <Michiel.Salters@logicacmg.com>
Date: Thu, 19 May 2005 15:35:28 CST Raw View
Ron House wrote:
>... to generate:
>
> <div id="fred" align="left">
>
> I want to write in C++:
>
> cout << div().id("fred").align("left");
>
> The payoff is for the complicated classes like table, so please don't
> complain that this would be as easy to write in html.
>
> The trouble is that the id attribute is available for many html
> elements, and so would be most logically provided in an ancestor
class
> from which div is descended. But the align element is specific to div
> (and some others). But if (as I had to do at present) I define id as
a
> method of the ancestor, it must return the ancestor type, and then
> adding the align call generates a syntax error.
Oh - if that's your problem, you should have asked.
template<typename TAG> struct has_id {
TAG& id(char const*) {
...
return *static_cast<TAG*>(this);
}
};
class div :
public has_id<div>, has_align<div> {
...
};
Your ancestor can return your type.
HTH,
Michiel Salters
---
[ 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: house@usq.edu.au (Ron House)
Date: Thu, 19 May 2005 20:34:58 GMT Raw View
Stefan Strasser wrote:
> Ron House schrieb:
>
>>
>> To make it work, we would need to define << something like this:
>>
>> typeof(out)& operator<<(ostream& out, const char* str) {
>> ...
>> return out;
>> }
>>
>
> snip
>
>>
>> What would the compiler have to do, in terms of generating new code?
>> Absolutely nothing. All that is needed is some way to get past the
>> type checking.
>>
>
> you get completely past type checking this way:
> the returned type can not be compile-type checked.
Your remark is simply wrong. For all intents and purposes, the argument
itself will be the only thing that can be returned (because only that
can be statically guaranteed to be a subtype of out, given that we don't
know in the function what will actually be passed as an argument). Would
it be clearer if we made that official, and wrote the above as:
returning(out) operator<<(ostream& out, const char* str) {
...
return out;
}
> the function above is allowed to return any type which is derived from
> "ostream", which does not necessarily match type "ofstream" in your
> example:
No. The entire point of the feature is that it is guaranteed to be
derived from the type of the argument, NOT from ostream. Have you looked
at Aiken's Cool language? It is a working compiler that _actually does
it_ for the owning object of a class method. All I have done is take
Aiken's feature and rewrite it in C++ notation and apply it to arguments.
> int main() {
> ofstream f("fred.txt");
> (f << "hello\n").close();
> return 0;
> }
>
> you loose type safety.
> besides that there _is_ a need to generate code. downcasting is only a
> no-op with non-virtual inheritance and an empty class.
There is no downcasting involved. It is a static compiler check, not a
runtime feature. The operator code was statically checked to ensure it
returned the argument or a descendant, so the return value is an fstream
or a descendant, which certainly will have a close method. This is a
statically determined dead certainty, with no runtime implications
whatsoever.
--
Ron House house@usq.edu.au
http://www.sci.usq.edu.au/staff/house
---
[ 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: AlbertoBarbati@libero.it (Alberto Barbati)
Date: Fri, 20 May 2005 01:50:07 GMT Raw View
Ron House wrote:
> Alberto Barbati wrote:
>> Ron House wrote:
>> Hmmmm... So you are telling me that you want the *same* function
>> A::mymethod to return two different types and the compiler should choose
>> which type according to type of one of the functions parameters (the
>> implicit "this" parameter, in this case)?
>
> No, it should return one type, but that type is specified to be the type
> of a parameter, not a fixed type. In C++, this is of course only useful
> with pointers and references, as they can pass descendants.
Maybe I didn't express myself well, but that's exactly what I was trying
to say, so we agree on this point.
> Okay, here's a simple program that currently gives a syntax error, but
> which obviously would work if allowed to compile:
>
> #include <iostream>
> #include <fstream>
> using namespace std;
>
> int main() {
> ofstream f("fred.txt");
> (f << "hello\n").close();
> return 0;
> }
Sorry, but this code looks horribly obfuscated to me. I don't buy this,
but I agree that it's a matter of taste.
> The actual real-world example I found was when I wrote a package for
> generating html. I wanted a method for every attribute a given html tag
> had. So, to generate:
>
> <div id="fred" align="left">
>
> I want to write in C++:
>
> cout << div().id("fred").align("left");
>
> <snip>
Oh, that's way more interesting. This example gives you the point. I
also agree that the free-function approach I proposed in a previous post
is not suitable to handle this case.
>
>> 2) if this feature is taken in full generality (any expression in typeof
>> depending on any parameter) I find it hard to believe that such thing
>> can be implemented without troubles...
>
> In effect the only freedom you get is descendants in references and
> pointers.
It seems to me there is a difference between "the freedom one could get"
(the full generality I was talking about, which is probably too much)
and "the freedom *you* want to get" (which is only about descendants).
> pointers. It is easy to do, and I have done it, as I said, in writing
> the compiler for Aiken's beautiful toy language, Cool. How embarrassing
> that a compiler class toy language can do something so neat and C++ can
> not! Aiken's writeup tells you the exact rules that need to be
> implemented to make it work. They simply are not hard - dead easy in fact.
You have expressed this concept quite a number of times in this thread
and I think you succeeded in explaining it very well. Repeating it again
and again doesn't make it sound more convincing and it's not a very
constructive way of argumentation, IMHO.
Being constructive, I see two different idioms in your "suggestion". The
first may really deserve a formal proposal, while the second can already
be achieved within current language.
1) "static-covariant" return type for member functions based on the
static type of the this parameter. You have been referring to this case
with the "typeof(*this)" expression. I agree that this feature could be
useful, but the proposed syntax has the following problems:
* it introduces a new keyword, in particular it introduces a keyword
which has been already used in other proposals with a different semantic
and so it may be a source of great confusion
* the syntax looks like it can be used with a greater flexibily than
it's really intended. In fact, *in this context*, only "*this" could be
put as argument to typeof (for expressions different than "*this" please
see below at number 2).
In this case, it seems to me that the syntax proposed by Hyman Rosen is
much more suitable. It does not introduce a new keyword, it has clearer
and well-defined semantic and is probably very easy to implement too.
Consider this:
class A
{
public:
this function() { /* function block without "return *this" */ }
};
the body of a "this" member function need not have a return statement
and if it has one it should not be followed by an expression (as in
"return;"). The magic happens all at the call site: the value of an
expression like "expr.member()" shall be equal to "expr", with "expr"
being evaluated once.
In a formal proposal we should be more explicit about subtleties such
has lvalues and rvalues. Probably we should also add a const version.
In fact this case could also be handled with templates and the curiously
recurring pattern, but that require a lot of extra code and this fact
makes the proposal a real time-saver.
2) "static-covariant" return type for free functions and for member
function *not* based on the this parameter. This case can be easily
handled with templates, with well-defined semantic and more
generality and flexibility. Your proposal:
typeof(out)& operator<<(ostream& out, const char* str) {
...
return out;
}
can easily be written as:
template <class T>
T& operator<<(T& out, const char* str) {
...
return out;
}
You can also force T to be a class derived from ostream, by using SFINAE
tricks like enable_if, if you want. If and when we will have concepts in
the language, there may be more refined ways to achieve the same (I'm
not following the "concepts" proposal, so I cannot tell).
Therefore, I think this case doesn't need a special treatment.
Any thoughts?
Alberto
---
[ 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: house@usq.edu.au (Ron House)
Date: Fri, 20 May 2005 04:13:46 GMT Raw View
Hyman Rosen wrote:
> Ron House wrote:
>
>> No, it should return one type, but that type is specified to be the
>> type of a parameter, not a fixed type.
>
>
> Assuming that we only want to return *this or a reference parameter
> (rather than creating a new object of the appropriate derived type),
> the function itself doesn't have to return anything - the compiler
> can just substitute in the parameter/object multiple times.
I think this is an important observation. My reservation is indeed the
caviat you mention. I have a niggle, which might be unfounded, as to
whether one will be able to create an object dynamically of the same
type as another object. For example, if a feature were in future
provided that allowed something like:
typeof(out)();
Then it might be possible for some other object to meet the requirement.
It could still be checked statically, though, but not if we change the
syntax on the assumption that the return will be the argument itself.
>> ofstream f("fred.txt");
>> (f << "hello\n").close();
>
>
> Would be turned by the compiler into
> ofstream f("fred.txt");
> f << "hello\n";
> f.close();
>
>> cout << div().id("fred").align("left");
>
>
> Would be turned by the compiler into
> Div_t &d = div();
> d.id("fred");
> d.align("left");
> cout << d;
>
> Notice that the id and align functions don't actually need to return
> anything at all, because the compiler simply uses an object available
> at the call site as the pseudo return value.
Yes. This could be a big win in some circumstances.
> So all we need is syntax. For members returning *this, I already
> suggested using just 'this' as the return type. For parameters,
> how about allowing one of them to be designated as 'return'?
>
> template<typename T>
> void operator<<(return mystream &out, const T &value)
> { static_cast<ostream &>(out) << value; }
If such an argument were provided, then it would seem logical to omit
the return type entirely, as it is specified elsewhere, and isn't void.
Getting back to the more general syntax, though, there is no reason to
forbid something like this:
typeof(a1) func(X& a1, typeof(a1)& a2) {
...
if(...) return a1; else return a2;
}
In this case, the declaration promises that a2 must be a subtype of a1's
actual parameter, so the if statement may return either a1 or a2, as
both of then must be subtypes of a1's actual.
In the call, assuming An is a strict subtype of A(n-1) we might have:
A1 a;
A2 b;
A3 c;
A1 *ap;
B1 *bp;
.
func(a, a); // legal
func(a, b); // legal
func(a, *ap); // legal
func(*ap, *bp); // illegal - ap might not actually point to a
// supertype of *bp's type
func(b, c).A2method(); // legal - any possible return must own
// an A2method.
func(b, c).A3method(); // illegal, even if we think we can
// predict that the if will return c, because this is
// strictly a static type check.
This is such an important feature I would support both your suggestion
and my original, without in any way considering it overkill. I have seen
the lack of this feature cost me hundreds of repeated method
declarations. I did it with macros, but that is beside the point. This
can sometimes be simulated with templates, sometimes not, but it is in
principle nothing to do with templates. No code is being recompiled;
even for aesthetic reasons, that is enough to reject template solutions
as substitutes for this feature. I think I will try to compose a formal
proposal, incorporating your suggestions as well as mine, if you
approve; and I'll post it back here before submission for further
discussion.
--
Ron House house@usq.edu.au
http://www.sci.usq.edu.au/staff/house
---
[ 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: "Samee Zahur" <samee.zahur@gmail.com>
Date: Thu, 19 May 2005 23:12:14 CST Raw View
I still seem to hold differing opinions on certain issues:
> There is absolutely no difference in code generated because the
> argument can already refer to a descendant.
I slight difference has to exist. Let's look at it this way:
currently, the keyword "this" ALWAYS has a compile-time type ostream*
(or base* or whatever). The moment you write typeof(*this) to refer to
a derived type reference, it makes ideas go a bit uneasy on me. Then,
the compiler would have no way of knowing what this actual return type
is - not until it sees the function call. So when it reaches the
statement return *this, it will have no way of figuring out the exact
conversion to apply before returning the pointer reference - not until
it sees the function call. So at least this last part of code
generation has to be deferred until the actual call. It will allow
every single code you have shown as examples so far.
Now, the obvious solution to this that comes to mind is this: Let the
function always return ostream&, but a typeof(*this) or typeof(out) in
the function prototype would prompt the caller to do the actual
conversion back to the parameter type. And this is exactly what Dave
Harris has done in his post - splitting the function up into parts
which can be compiled at once and part which has to be compiled later.
Once you make up a single template funciton, it will serve you for a
large number of cases. Codes like these will be valid as well.
> ofstream f("fred.txt");
> (f << "hello\n").close();
So once again, what you are proposing *is* merely a syntatic sugar for
something we already can do ;) By the way I see it, you are asking for
a small part of the facilities templates already provide, while
avoiding the use of the word 'template'
Samee
---
[ 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: house@usq.edu.au (Ron House)
Date: Fri, 20 May 2005 21:59:51 GMT Raw View
msalters wrote:
> Ron House wrote:
>
>>... to generate:
>>
>><div id="fred" align="left">
>>
>>I want to write in C++:
>>
>>cout << div().id("fred").align("left");
>>
>>The payoff is for the complicated classes like table, so please don't
>>complain that this would be as easy to write in html.
>>The trouble is that the id attribute is available for many html
>>elements, and so would be most logically provided in an ancestor
> class
>>from which div is descended. But the align element is specific to div
>>(and some others). But if (as I had to do at present) I define id as
> a
>>method of the ancestor, it must return the ancestor type, and then
>>adding the align call generates a syntax error.
>
> Oh - if that's your problem, you should have asked.
>
> template<typename TAG> struct has_id {
> TAG& id(char const*) {
> ...
> return *static_cast<TAG*>(this);
> }
> };
> class div :
> public has_id<div>, has_align<div> {
> ...
> };
>
> Your ancestor can return your type.
I greatly appreciate any suggestions to help me improve the class system
I have written, so thank you for taking the time to help. I point out,
though, that this (though very helpful to me!) is not a rebuttal of the
need for the feature I proposed. Taking html for a concrete example.
Have you had the shocking experience of reading the standard? There are
literally hundreds of tags, and scores of attributes common to all or to
large subchunks of the tags. Even doing what you suggest would generate
incredible bloat, all quite unnecessary, if only the compiler could
understand what it is the methods intend to return.
--
Ron House house@usq.edu.au
http://www.sci.usq.edu.au/staff/house
---
[ 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: house@usq.edu.au (Ron House)
Date: Fri, 20 May 2005 22:00:30 GMT Raw View
Alberto Barbati wrote:
> Ron House wrote:
>>int main() {
>> ofstream f("fred.txt");
>> (f << "hello\n").close();
>> return 0;
>>}
>
> Sorry, but this code looks horribly obfuscated to me. I don't buy this,
> but I agree that it's a matter of taste.
Indeed. It was only a quickly conceived example. But who knows what it
might be convenient for an automated program generator to construct?
>>>2) if this feature is taken in full generality (any expression in typeof
>>>depending on any parameter) I find it hard to believe that such thing
>>>can be implemented without troubles...
>>
>>In effect the only freedom you get is descendants in references and
>>pointers.
> It seems to me there is a difference between "the freedom one could get"
> (the full generality I was talking about, which is probably too much)
> and "the freedom *you* want to get" (which is only about descendants).
I suspect we have both misunderstood each other, and I apologise for not
making the meaning clear. The OO philosophy is to use inheritance for
polymorphism, and it works incredibly well. The goal is only to extract
the greatest traction from inheritance polymorphism, not to make the big
mistake of trying to turn C++ into a 'first class types' language.
> You have expressed this concept quite a number of times in this thread
> and I think you succeeded in explaining it very well. Repeating it again
> and again doesn't make it sound more convincing and it's not a very
> constructive way of argumentation, IMHO.
True. I repeated it because I misunderstood your earlier comment. My
apologies.
> Being constructive, I see two different idioms in your "suggestion". The
> first may really deserve a formal proposal, while the second can already
> be achieved within current language.
>
> 1) "static-covariant" return type for member functions based on the
> static type of the this parameter. You have been referring to this case
> with the "typeof(*this)" expression. I agree that this feature could be
> useful, but the proposed syntax has the following problems:
>
> * it introduces a new keyword, in particular it introduces a keyword
> which has been already used in other proposals with a different semantic
> and so it may be a source of great confusion
Thanks for that! I'll make sure I fix that problem.
> * the syntax looks like it can be used with a greater flexibily than
> it's really intended. In fact, *in this context*, only "*this" could be
> put as argument to typeof (for expressions different than "*this" please
> see below at number 2).
I think that's true, given the language as it currently is.
> In this case, it seems to me that the syntax proposed by Hyman Rosen is
> much more suitable. It does not introduce a new keyword, it has clearer
> and well-defined semantic and is probably very easy to implement too.
> Consider this:
>
> class A
> {
> public:
> this function() { /* function block without "return *this" */ }
> };
I like Hyman's suggestion here, which merits consideration on its own.
The need to avoid new keywords is great, so I'll have to do some
thinking to find another way to express the general concept.
> the body of a "this" member function need not have a return statement
> and if it has one it should not be followed by an expression (as in
> "return;"). The magic happens all at the call site: the value of an
> expression like "expr.member()" shall be equal to "expr", with "expr"
> being evaluated once.
I only just woke up to the brilliance of Hyman's idea this morning. The
point is, I think, that there is a great deal of extra leverage that one
can get from implementing dynamic polymorphism via inheritance, and C++
isn't currently getting all that it could.
> In a formal proposal we should be more explicit about subtleties such
> has lvalues and rvalues. Probably we should also add a const version.
Good point.
> In fact this case could also be handled with templates and the curiously
> recurring pattern, but that require a lot of extra code and this fact
> makes the proposal a real time-saver.
It is of course a great compliment for C++ that it has grown in its
topsy turvy way, with input from so many people, in reverse order from
the standardisers, the HP geniuses (IMHO), Stroustrup, Richie, right
back to BCPL if we want to go that far. And the result is a language
that has the ability to re-use a feature such as templates to get the
effect of something quite different that is still missing. That's a
remarkable thing. But it doesn't mean we should omit to provide a clean
feature for the purpose.
> 2) "static-covariant" return type for free functions and for member
> function *not* based on the this parameter. This case can be easily
> handled with templates, with well-defined semantic and more
> generality and flexibility. Your proposal:
>
> typeof(out)& operator<<(ostream& out, const char* str) {
> ...
> return out;
> }
>
> can easily be written as:
>
> template <class T>
> T& operator<<(T& out, const char* str) {
> ...
> return out;
> }
>
> You can also force T to be a class derived from ostream, by using SFINAE
> tricks like enable_if, if you want. If and when we will have concepts in
> the language, there may be more refined ways to achieve the same (I'm
> not following the "concepts" proposal, so I cannot tell).
> Therefore, I think this case doesn't need a special treatment.
>
> Any thoughts?
You make a strong case for this one. To me it boils down to what a
template actually is. Conceptually, to me, it is basically automated
code-writing. A version of the template will be written for the types
involved in the specific call, and will then be compiled. After that,
all sorts of optimisations will be applied, such as redundant code
elimination. To make this work, the body of the template function must
be available to each compilation unit. But in the case under discussion,
we actually know in advance that there is exactly one version of the
function, that all the generated versions, after all optimisations, will
boil down to exactly one executable binary version, which we could have
compiled once only as plain code in a separate unit. To me that is a
quite unreasonable way to expect the system to proceed, when an
extremely cheap (in terms of added logic) enhancement to the compiler
will let us tell it more precisely what we are trying to do with our
code. The real killer is language complication. You rightly mention the
keyword issue. A formal proposal would have to take the greatest care to
provide the feature with the simplest possible syntax. I suppose the
ball is in my court!
--
Ron House house@usq.edu.au
http://www.sci.usq.edu.au/staff/house
---
[ 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: house@usq.edu.au (Ron House)
Date: Fri, 20 May 2005 22:00:53 GMT Raw View
Samee Zahur wrote:
> I still seem to hold differing opinions on certain issues:
>
>
>>There is absolutely no difference in code generated because the
>>argument can already refer to a descendant.
>
> I slight difference has to exist. Let's look at it this way:
> currently, the keyword "this" ALWAYS has a compile-time type ostream*
> (or base* or whatever). The moment you write typeof(*this) to refer to
> a derived type reference, it makes ideas go a bit uneasy on me. Then,
> the compiler would have no way of knowing what this actual return type
> is - not until it sees the function call. So when it reaches the
> statement return *this, it will have no way of figuring out the exact
> conversion to apply before returning the pointer reference - not until
> it sees the function call.
I really think you are missing something in your understanding
somewhere. At present, when the compiler sees "this", it does *not* know
that it will point to a base object at runtime. This means that during
compilation of the method, there is absolutely no difference in how it
must behave when processing the body of the method under my proposal,
with one single exception: it must apply a stricter type check on the
expression given in any return statements. That's it. Everything else is
exactly the same.
The processing of the call is done separately from processing the
function definition. There is no connection. At point of call, because
of the stricter check on the return type when compiling the definition,
it has more information available about the actual type of the returned
object. And remember, at runtime it might be any descendant even in the
language as it is, so having *more* information available can hardly
make it *harder* for the compiler to determine what to do! If I know how
to handle any descendant of base (and I must, in the language as it
stands!) then by the relentless application of logic, I must know how to
handle any descendant of one particular descendant of base.
>...
> So once again, what you are proposing *is* merely a syntatic sugar for
> something we already can do ;) By the way I see it, you are asking for
> a small part of the facilities templates already provide, while
> avoiding the use of the word 'template'
I have written about my philosophical objections to using the
steamroller template feature to handle a dead-easy typing problem in
another article today. But to answer your specific point, as the
explanation above shows, this is not equivalent to templates in any way.
Absolutely none of the contents of the function definition need be
available to the compiler at point of call.
--
Ron House house@usq.edu.au
http://www.sci.usq.edu.au/staff/house
---
[ 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: brangdon@cix.co.uk (Dave Harris)
Date: Fri, 20 May 2005 22:02:08 GMT Raw View
house@usq.edu.au (Ron House) wrote (abridged):
> Of course it is impossible for me to prove a negative. If C++ has a
> feature that Cool does not, which somehow invalidates this analysis,
> you tell me what it is.
The only articles on Cool I could find were in postscript, and I don't
have a postscript browser on this machine. Does Cool support repeated
inheritance? Does it provide a clone() method which is guaranteed to
return the same (dynamic) type as its argument?
Your original description implied the function could return either the
parameter object, or another object with the same type. It would be
convenient if clone were supported:
typeof(arg1) add( Arg *arg1, Arg *arg2 ) {
Arg *result = arg1->clone();
*result += *arg2;
return result;
}
which would be statically typesafe if clone is declared like:
typeof(this) Arg::clone();
And it is also easy for the compiler to check that a given implementation
of clone() returns a suitable type for its class, even if it has "return
new Arg(*this);". The problem is that a suitable type for a base class may
not be suitable for a derived class.
I think it suffices to have a rule which says virtual typeof(this)
functions must be overridden in the most derived class; and non-virtual
ones must return either this or an object known to be typeof(this) (ie one
got from another typeof(arg) function).
Given that, then we can type-check typeof routines which return different
objects, so we can't use the trick of returning void and reusing the
actual parameter instead. So we have a function which returns a base class
and the compiler needs to downcast that to the type of the argument. And
it seems to me that the downcast can be ambiguous in the case of repeated
inheritance.
Another issue is that we really want clone() to be callable on const
objects but return a non-const result. So we need some mechanism to remove
const from the typeof(this). We probably want to support smart pointers as
well. A smart pointer to Derived typically does not inherit from a smart
pointer to Base, so we probably need to replace the notion of inheritance
with some notion of "is convertible to".
So there are complications. I don't know how Cool deals with them. I do
know many toy languages don't bother with repeated inheritance, and they
provide a universe root class which provides a system-defined clone()
method, and they don't have const, and they don't have user-defined smart
pointers. (For example, Java is a toy language by this criteria.)
Implementing your new feature would be easier in such a language than in
C++.
-- Dave Harris, Nottingham, UK.
---
[ 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: jdennett@acm.org (James Dennett)
Date: Sat, 21 May 2005 21:22:06 GMT Raw View
Ron House wrote:
> Hyman Rosen wrote:
>
>> Ron House wrote:
>>
>>> No, it should return one type, but that type is specified to be the
>>> type of a parameter, not a fixed type.
>>
>>
>>
>> Assuming that we only want to return *this or a reference parameter
>> (rather than creating a new object of the appropriate derived type),
>> the function itself doesn't have to return anything - the compiler
>> can just substitute in the parameter/object multiple times.
>
>
> I think this is an important observation. My reservation is indeed the
> caviat you mention. I have a niggle, which might be unfounded, as to
> whether one will be able to create an object dynamically of the same
> type as another object. For example, if a feature were in future
> provided that allowed something like:
>
> typeof(out)();
>
> Then it might be possible for some other object to meet the requirement.
> It could still be checked statically, though, but not if we change the
> syntax on the assumption that the return will be the argument itself.
We can certainly return another object of the same dynamic type
as *this in some cases, without needing to create one dynamically:
if (typeid(out) == typeid(typeX)) {
static typeX x;
return x;
} else {
return *this;
}
This would, of course, be disallowed if the return value was
restricted to being a specified argument (but in that case would
we want to allow "this" as well as "*this" to be returned, and
similarly for other pointer arguments?)
-- James
---
[ 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: house@usq.edu.au (Ron House)
Date: Mon, 23 May 2005 10:42:07 GMT Raw View
James Dennett wrote:
> We can certainly return another object of the same dynamic type
> as *this in some cases, without needing to create one dynamically:
>
> if (typeid(out) == typeid(typeX)) {
> static typeX x;
> return x;
> } else {
> return *this;
> }
Yes, that's the eaxmple I couldn't quite put my finger on. So C++ will
allow us to obtain something else of a certain object's type.
> This would, of course, be disallowed if the return value was
> restricted to being a specified argument (but in that case would
> we want to allow "this" as well as "*this" to be returned, and
> similarly for other pointer arguments?)
I think you are convincing me we need both proposals. My html-writing
classes always return *this, so they would be quite happy restricting to
a single argument and getting better optimisation. But you have now
proved that the other can happen too in existing C++, not to mention new
features in future versions.
--
Ron House house@usq.edu.au
http://www.sci.usq.edu.au/staff/house
---
[ 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: house@usq.edu.au (Ron House)
Date: Tue, 10 May 2005 04:37:00 GMT Raw View
It often happens that one want to propagate an argument or an object
type as the result of a function. When the arg is a ref or ptr, the
actual arg might not be the declared type, but a descendant. It would be
very useful to be able to propagate that information into the result
type. For example,
typeof(out) operator<<(ostream& out, const Person& p) {
... output the Person data ...
return out;
}
Now, if the << operator is used as part of a larger expression, the
compiler might be able to deduce stronger limits on the type of the
entire expression than it can if the result type is merely ostream.
I ran into this one while writing a parent class with methods that were
designed to be 'strung together', each returning a ref to the owning
object, *this. The classes would string together nicely, but when used
in a descendant, the compiler thought the type was the ancestor, so I
had to write inline 'shell' functions in every descendant to merely call
the ancestor but give it the more descriptive type. It would have been
great to be able to write something like:
typeof(*this)& mymethod() { ... return *this; }
Of course, I don't think it is beyond credibility that I have entirely
overlooked some way that already exists to accomplish this...
--
Ron House house@usq.edu.au
http://www.sci.usq.edu.au/staff/house
---
[ 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 ]