Topic: the most dissatisfying part of c++


Author: shap@thebeach.wpd.sgi.com (Jonathan Shapiro)
Date: 2 Aug 90 18:17:18 GMT
Raw View
In article <27634@netnews.upenn.edu>, limsoon@saul.cis.upenn.edu
(Limsoon Wong) writes:

> the program below is a contrived one.   it cannot  be compiled.
> the reason is a type fault. however, it exhibits a very central
> characteristic of update.    something should be  done to allow
> this kinds of programs.           ---limsoon.
>
>
>
> #include <stream.h>
> class a {
>   public:
>     a& test() {
>  // do some modification to the state of this object,
>  // then ...
>  return *this;
> };
> class b : public a {
>   public:
>     int b;
> };
> main() {
>   b B;
>   B = B.test();
>   // type violation despite the fact that
>   // `B' and `B.test()' are the same object.
> }

This has surprising consequences in the face of virtuals, since the
caller thinks they are getting an instance of an A, not a B.  From a
static typechecking angle it works, and the parallel argument for
returning pointer to derived in place of pointer to base follows.

However, in the face of multiple inheritance this doesn't work at all.

Jon




Author: jeh@cs.rit.edu (Jim Heliotis)
Date: 3 Aug 90 18:51:39 GMT
Raw View


Author: dl@g.g.oswego.edu (Doug Lea)
Date: 3 Aug 90 14:55:20 GMT
Raw View
My `Customization in C++' paper in the '90 Usenix C++ Proceedings
discusses a reasonably clean, but extensive proposal that would
support the desired usage:

#include <stream.h>
class a {
  public:
    template a& test() {  // *** Note the qualifier ***
 // do some modification to the state of this object,
 // then ...
 return *this;
};

class b : public a {
  public:
    int b;
};

main() {
  b B;
  B = B.test(); // *** Legal under customization ***
  // type violation despite the fact that
  // `B' and `B.test()' are the same object.
}


While I played down the expressivity aspects of the type qualifier
sense of `template' and concentrated on optimization consequences,
the proposed constructs are indeed extremely valuable for writing
container classes and the like.

I'd love to hear from anyone concerning the standardizability of
the proposal.

-Doug
--
Doug Lea, Computer Science Dept., SUNY Oswego, Oswego, NY, 13126 (315)341-2688
email: dl@g.oswego.edu            or dl@cat.syr.edu
UUCP :...cornell!devvax!oswego!dl or ...rutgers!sunybcs!oswego!dl




Author: dl@g.g.oswego.edu (Doug Lea)
Date: 4 Aug 90 12:02:05 GMT
Raw View
From: jeh@cs.rit.edu (Jim Heliotis)

> > The change to the existing rules is to allow overidings of virtual
> > functions to return a type to which a standard conversion may be
> > applied to obtain the inherited return type.  ("standard conversion"
> > may be too general; perhaps only derived-to-base standard conversions
> > should be applicable).  The appropriate member function signature would
> > be chosen according to the static type of the pointer through which
> > it is invoked.
> >
>
> OK, I'll put my two cents in:
>
> - Look at Eiffel.  They do this with some sort of type attribute called
>   something like "SAME AS", so you could declare you are returning an
>   object the same type as one of your arguments.
>   This works because I believe Eiffel's implementation includes class ids
>   in objects, but I could be wrong.

But look at W. Cook's ECOOP '89 paper to see how `LIKE CURRENT' (`SAME AS')
can create holes in a type system. Making something like `typeof' a first
class primitive is equally problematic because an object of
a derived class may be said to have many types, from leaf to root of an
inheritance tree.

> - It appears to be hard to solve this problem without dynamic class ids.
>
> - Most C++ classes relevant to this need /have\ dynamic class ids -- their
>   virtual function table addresses!  Now, this does not help much when dealing
>   with persistent objects, but is it worth a shot?

Not entirely relevant: In C++ you can either get static (compile-time)
resolution or dynamic (run-time) resolution. With non-virtual
functions/classes, you are always limited to static resolution, for
better and worse, but the same rules apply to the extent to which
types are known at compile time. So programmers don't absolutely need
dynamic ids as long as they know that static resolution does the right
thing in a particular application.

While I'm at it, here's a different angle towards explaining the
basic idea. Suppose the original example were redone so that
a::test() returned a pointer, and pointers were used in main().

#include <stream.h>
class a {
  public:
    a* test() {  // *** original said `a& test()'
 // do some modification to the state of this object,
 // then ...
 return this; // *** original said `return *this;'
};
class b : public a {
  public:
    int b;
};
main() {
  a* B = new b;  // *** original said `b B;'
  B = B->test(); // *** original said `B = B.test();'
}

This has no type violations. The `customization' proposal extends this
usage to apply to references and values by adding a type qualifier
requesting such behavior, along with rules to support it.

-Doug
--
Doug Lea, Computer Science Dept., SUNY Oswego, Oswego, NY, 13126 (315)341-2688
email: dl@g.oswego.edu            or dl@cat.syr.edu
UUCP :...cornell!devvax!oswego!dl or ...rutgers!sunybcs!oswego!dl




Author: limsoon@saul.cis.upenn.edu (Limsoon Wong)
Date: 6 Aug 90 16:04:49 GMT
Raw View
first a message from jeremy grodberg,

--------------------------------X----------------------------

Sorry I can't post this, but our posting software is broken. You can post this
for me if you like.

Why would you want to have a function that returns an a& be allowed to
replace a b object *as a default*.  It is inherently dangerous, since
the function only affects the "a" part of b, which, for operators like
+ and = is probably not sufficient.  The sample code you wrote shows that
the function test really should be defined as returning void, so that you
would write:

B.test();
B = B;


--

Jeremy Grodberg
jgro@apldbio.com    "Beware: free advice is often overpriced!"

--------------------------------X----------------------------

my reply:

the suggestion is a valid alternative, though the assignment
`B = B' is unnecessary.  however, that is not what
i am getting at.

i am mere pointing out what i consider the part of c++ that
requires improvement: unnecessary loss of type information.
it is clear that before and after performing `B.test()',
the object identity of `B' remains invariant. hence, `B'
and `B.test()' should be given the same type. but `B.test()'
is given type `a', which is much weaker than `b'.

it is very annoying to have an object that gets progressively
weaker every time you do something to it.


more comments on other replies:

the template suggestion was a very good try. but it does
not work in general. nevertheless, modifications to c++'s
type specification along tha line seems to be the most
satisfactory solution.

i would like a keyword `SUB' to be used as type in class
specification in the following way.

 class C { public:
  (...SUB...) f(..);
  A p(..SUB...);
 };

 class D : public  C {
  something
 };

where `(...SUB...)' is any type expression involving `SUB'.
let `d' be a class `D' object. i want

 d.f be given the type   *   (..) -> (...D...)
 d.p be given the type   *   (..D...) -> A

that is, `SUB' is a place holder. it gets replaced by the
corresponding subclass. i think it does not complicate type
checking or implementation; in particular, its with multiple
inheritance. therefore, i do wish something like this be provided.

limsoon.




Author: limsoon@saul.cis.upenn.edu (Limsoon Wong)
Date: 31 Jul 90 14:46:47 GMT
Raw View
the program below is a contrived one.   it cannot  be compiled.
the reason is a type fault. however, it exhibits a very central
characteristic of update.    something should be  done to allow
this kinds of programs.           ---limsoon.



#include <stream.h>
class a {
  public:
    a& test() {
 // do some modification to the state of this object,
 // then ...
 return *this;
};
class b : public a {
  public:
    int b;
};
main() {
  b B;
  B = B.test();
  // type violation despite the fact that
  // `B' and `B.test()' are the same object.
}




Author: lijewski@batcomputer.tn.cornell.edu (Mike Lijewski)
Date: 31 Jul 90 17:44:43 GMT
Raw View
In article <27634@netnews.upenn.edu> limsoon@saul.cis.upenn.edu.UUCP (Limsoon Wong) writes:

>the program below is a contrived one.   it cannot  be compiled.
>the reason is a type fault. however, it exhibits a very central
>characteristic of update.    something should be  done to allow
>this kinds of programs.           ---limsoon.

I disagree completely that such programs should be allowed.  Since test()
returns a `a&', basically what you are asking is that there be a implicit
conversion from a base class to a derived class.  No way should this be
allowed.  If you really want the below program to work, one possibility
would be to add the following constructor definitions to the class
declaration of b:

  b() {};
  b(a) {};


--
Mike Lijewski  (H)607/277-0394 (W)607/254-8686
Cornell National Supercomputer Facility
ARPA: mjlx@eagle.cnsf.cornell.edu  BITNET: mjlx@cornellf.bitnet
SMAIL:  1122 Ellis Hollow Rd. Ithaca, NY  14850




Author: schemers@vela.acs.oakland.edu (Roland Schemers III)
Date: 31 Jul 90 18:18:10 GMT
Raw View
In article <27634@netnews.upenn.edu> limsoon@saul.cis.upenn.edu.UUCP (Limsoon Wong) writes:
>
>the program below is a contrived one.   it cannot  be compiled.
>the reason is a type fault. however, it exhibits a very central
>characteristic of update.    something should be  done to allow
>this kinds of programs.           ---limsoon.
>
>#include <stream.h>
>class a {
>  public:
>    a& test() {
> // do some modification to the state of this object,
> // then ...
> return *this;
>};
>class b : public a {
>  public:
>    int b;
>};
>main() {
>  b B;
>  B = B.test();
>  // type violation despite the fact that
>  // `B' and `B.test()' are the same object.
>}

I for one do not see where 'B' and 'B.test()' are the same object. The
function B.test() clearly returns a reference to an 'a' object. What
you could do is:

class b : public a {
  public:
    int b;
    b &test() { a::test(); return *this; }
};

(Forgive me if the syntax is not correct)

I understand your point of view though, and I think it would be nice
if there was some construct in C++ to do what you want. I know some of
my classes could use it. Maybe something like:

class a {
  public:
    virtual& test() {
 // do some modification to the state of this object,
 // then ...
 return *this;
};

In otherwords, make the return type 'virtual' like functions.
In this case, the virtual means the type of the class. So when class
'b' is derived from class 'a', all virtual return types in class 'a'
will now return type 'b'.

Of course this we be very ugly to implement, and is sort of like
parametric return types.

Actually, would templates solve this problem?

Roland
--
Roland J. Schemers III                              Systems Programmer
schemers@vela.acs.oakland.edu (Ultrix)              Oakland University
schemers@argo.acs.oakland.edu (VMS)                 Rochester, MI 48309-4401
"Get off your LEF and do something!"                (313)-370-4323




Author: hopper@ux.acs.umn.edu (hopper)
Date: 31 Jul 90 22:49:36 GMT
Raw View
In article <10599@batcomputer.tn.cornell.edu> lijewski@tcgould.tn.cornell.edu (Mike Lijewski) writes:
>In article <27634@netnews.upenn.edu> limsoon@saul.cis.upenn.edu.UUCP (Limsoon Wong) writes:
>
>>the program below is a contrived one.   it cannot  be compiled.
>>the reason is a type fault. however, it exhibits a very central
>>characteristic of update.    something should be  done to allow
>>this kinds of programs.           ---limsoon.
>
>I disagree completely that such programs should be allowed.  Since test()
>returns a `a&', basically what you are asking is that there be a implicit
>conversion from a base class to a derived class.  No way should this be
>allowed.  If you really want the below program to work, one possibility
>would be to add the following constructor definitions to the class
>declaration of b:
>
>  b() {};
>  b(a) {};

 I propose that there should be a generic class declaration for a
method that returns the same type as the class which it is part of.

Example:

class A {
   me &func(...); // This function modifies a.
   // .
   // .
   // .  (More stuff)
};

class B : A {
   // .
   // .
   // .  (Even more junk)
};

main()
{
   B b;

   b = b.func();
}

 Of course calling it me isn't neccesarily a good idea, but there
still should be one.

(I believe this is what Mike Lijewski had in mind all along, but I'm not
sure.)

Have fun,
UUCP: rutgers!umn-cs!ux.acs.umn.edu!hopper   (Eric Hopper)
     __                    /)                       /**********************/
    / ')                  //                         * I went insane to   *
   /  / ______  ____  o  //  __.  __  o ____. . _    * preserve my sanity *
  (__/ / / / <_/ / <_<__//__(_/|_/ (_<_(_) (_/_/_)_  * for later.         *
Internet:              />                            * -- Ford Prefect    *
hopper@ux.acs.umn.edu </  #include <disclaimer.h>   /**********************/




Author: bobatk@microsoft.UUCP (Bob ATKINSON)
Date: 1 Aug 90 19:13:52 GMT
Raw View
Limsoon Wong writes:
>
>the program below is a contrived one.   it cannot  be compiled.
>the reason is a type fault. however, it exhibits a very central
>characteristic of update.    something should be  done to allow
>this kinds of programs.           ---limsoon.
>
>
>
>#include <stream.h>
>class a {
>  public:
>    a& test() {
> // do some modification to the state of this object,
> // then ...
> return *this;
>};
>class b : public a {
>  public:
>    int b;
>};
>main() {
>  b B;
>  B = B.test();
>  // type violation despite the fact that
>  // `B' and `B.test()' are the same object.
>}


This is a not-infrequent idiom.  Another example of its use is
creating a "copy" method for collections (what's the return type?)
What one would like to write is

 class Collection {
 public:
  virtual ??? Copy();
 };

which will then be overridden in derived classes to do the
appropriate copy operation.  Replacing the question marks with
"Collection*" is not acceptable, for then clients would loose
static type information, as in


 class Collection {
 public:
  virtual Collection* Copy();
 };

 class Set : public Collection {
 public:
  virtual Collection* Copy(); // override for implementation
 }

 //...

 Set* pset, pset2 = ...;

 pset = pset2->Copy(); // illegal!


An approach to solving this problem involves using _non-virtual_
member functions.


 class Collection {
 protected:
  virtual Collection* _Copy(); // The real copy function
 public:
  Collection* Copy() { return _Copy(); }

 };

 class Set : public Collection {
  virtual Collection *_Copy(); // override implemenation
 public:
  Set* Copy() { return (Set *)_Copy(); }
 }

 //...

 Set* pset, pset2 = ...;

 pset = pset2->Copy(); // legal now


In the context of the example Limsoon presented, we would write:

 class a {
 protected:
  virtual void _test() { /* do the real work here }
 public:
   a& test()  { _test(); return *this; }
 };

 class b : public a {
 public:
   b& test()  { _test(); return *this; }
 };

 main()
  {
  a A;
  b B;
  B = B.test(); // legal
  }


Yes, this is tedious for the class writer, but it makes client's job
a lot easier.  I personally agree, though, that it would be
worthwhile investigating making the following constructions legal:

 class Collection {
 public:
  virtual Collection * Copy();
 };

 class Set : public Collection {
 public:
  virtual Set *  Copy();    // illegal today
 };


At present, this gives an illegal virtual return type error.

The change to the existing rules is to allow overidings of virtual
functions to return a type to which a standard conversion may be
applied to obtain the inherited return type.  ("standard conversion"
may be too general; perhaps only derived-to-base standard conversions
should be applicable).  The appropriate member function signature would
be chosen according to the static type of the pointer through which
it is invoked.


 Bob Atkinson
 Microsoft

[Disclaimer: I am but one solitary opinionated C++ programmer]




Author: brownpc@sunne.crd.ge.com (Paul C Brown)
Date: 1 Aug 90 21:02:00 GMT
Raw View
In article <27634@netnews.upenn.edu>, limsoon@saul.cis.upenn.edu
(Limsoon Wong) writes:
|>
|>the program below is a contrived one.   it cannot  be compiled.
|>the reason is a type fault. however, it exhibits a very central
|>characteristic of update.    something should be  done to allow
|>this kinds of programs.           ---limsoon.
|>
|>
|>
|>#include <stream.h>
|>class a {
|>  public:
|>    a& test() {
|> // do some modification to the state of this object,
|> // then ...
|> return *this;
|>};
|>class b : public a {
|>  public:
|>    int b;
|>};
|>main() {
|>  b B;
|>  B = B.test();
|>  // type violation despite the fact that
|>  // `B' and `B.test()' are the same object.
|>}

Your proposed program is not "type" correct. The method "test" returns
an object
of type a, not of type b. Objects of type a do not exhibit all of the
properties of type b, and therefore are not valid values for variables
of type b. The
following version of main is type correct:

main() {
  b B;
  a A;
  A = B.test();
};

To accomplish what you were trying to do would require a second method on
class b, also named test, that returns an object of type b. If the original
method on class a does, indeed, simply modify the referenced instance, then
the new method could be as simple as:

b::test() {
  a temp;
  temp = this.test();
  return *this;
};

As you have observed, strong typing does not give you everything for
free!
Paul C. Brown    brownpc@crd.ge.com
GE Corporate Research & Development
Schenectady, New York




Author: boissier@irisa.fr (franck boissiere)
Date: 2 Aug 90 06:12:25 GMT
Raw View