Topic: Conceptual Constness


Author: linton@marktwain.rad.sgi.com (Mark Linton)
Date: 1 Oct 1992 16:02:29 GMT
Raw View
In article <BvF77n.972@world.std.com>, pkturner@world.std.com (Prescott K Turner) writes:
|>  class const_matrix {
|>  public:
|>   int identity ();
|>   ...
|>  };
|>  class matrix : public const_matrix ();
|>  public:
|>   matrix & operator = (const_matrix &);
|>   ...
|>  };
|>  // Declare a conceptually immutable matrix.
|>  const_matrix conceptually_immutable = ...
|>  int is_ident = conceptually_immutable.identity();
|>
|> I wouldn't call this a solution, only a workaround.

This is really the second solution I suggested (don't use const).
It is true that once you decide not to use const, you can try to use
subclassing as a replacement mechanism.

The problem is if I want to declare a function p(const const_matrix& m),
I can't call m.identity().  If I declare p(const_matrix& m) and call p(a*b)
then the compiler may give me a warning about passing a temporary
to a non-const ref.

|> And for most purposes
|> I prefer the indirection approach described by Joe Buck, because the
|> interfaces are more what people expect.

Indirection is fine when you can afford it.  I would rather write a cast
or maybe flush const altogether than write a fancy memory allocator,
but const usage is more a convention that programmers on a project
must agree on than an issue where there is one right approach.




Author: krc@wam.umd.edu (Kevin R. Coombes)
Date: Thu, 1 Oct 1992 17:04:27 GMT
Raw View
In article <1992Sep29.160411.9737@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John MAX Skaller) writes:
>In article <1992Sep29.140750.29438@wam.umd.edu> krc@wam.umd.edu (Kevin R. Coombes) writes:
>>[The]
>> operator[](int index)
>>should not be a member of a list class. Lists only provide sequential
>>access; Arrays provide random access.
>
> I dont agree really. If you can get the first element,
>and get the next element, then you can get the n'th element.
>As far as I'm concerned, a list is an array with 'insert' and 'remove'
                                 ^^^^
>operations.

Is that really an "IS-A"? Just like in public derivation of one class
from another? Would  you write

class List : public Array {
  // ...
};

for example? I'm just checking, because I don't want to read more into
your statement than you may have intended. For me, an Array is primarily
characterized by supplying constant-time random-access to any of its
elements. An Array class may supply other services (like inserting
objects at preallocated locations for later access, or bounds checking), but
those are not the defining properties. A List, on the other hand, is
primarily a sequential-access structure. From this point of view,
Arrays are usually implemented by the builtin arrays (note the lowercase),
possibly allocated dynamically. Lists can be built with arrays, but they
can also be implemented directly as linked lists, where objects are
allocated on the fly and put into nodes that also contain pointers to
the next object. Lists can, as you suggest, provide a random-access
operator using the more primitive operations of first and next, but
they don't have to do so. I find the claim that a List "IS-A" Array
(again, I apologize if I have misinterpreted that statement), to be
rather inflated. The two are equally primitive; one can be implemented
in terms of the other; forcing a particular dependence or implementation
seems to me to be to restrictive.

Kevin Coombes <krc@math.umd.edu>





Author: pete@genghis.borland.com (Pete Becker)
Date: Thu, 1 Oct 1992 16:22:38 GMT
Raw View
In article <1992Sep30.190912.21322@watson.ibm.com> mittle@siena.watson.ibm.com (Josh Mittleman) writes:
>pete@genghis.borland.com (Pete Becker) writes:
>
>> I take a different view of this.  An array is characterized by indexed
>> access.  It can be implemented with a list, it can be implemented with a
>> vector, it can be implemented with a btree, and it can be implemented in
>> many other ways.  But regardless of the implementation that's chosen,
>> operator[] is always available, and always has the same semantics.
>
>You are confusing terminology: An array is an implementation that can be
>used to support a variety of abstract data structures, including a vector,
>a stack, a queue, a dictionary, a heap, etc.

 No.  I'm not confused at all.  I used "array" and "vector" in exactly
the way that I meant to, and in a context in which their meanings are perfectly
clear.  To the best of my knowledge, there is no general agreement as to which
is which here, so I will continue to take pains to make my meaning clear,
rather than rely on obscure conventions.
 -- Pete




Author: swf@teradata.com (Stanley Friesen)
Date: 1 Oct 92 17:36:02 GMT
Raw View
In article <1adbr4INNkno@agate.berkeley.edu> jbuck@forney.berkeley.edu (Joe Buck) writes:
|In article <1ackm3INNno2@fido.asd.sgi.com> linton@marktwain.rad.sgi.com (Mark Linton) writes:
|>This subject has been visited in the past.  There have been several language
|>proposals, though I don't know if any are being taken seriously.
|
|I, for one, hope that the ~const proposal is taken seriously.  I'd like
|to go even further, and permit objects with constructors to be in ROM
|in cases where this is possible (it is possible if the initialization
|can be done at compile time and the relevant constructor is inline).
|This could already be done, except that the language in the ARM, as
|clarified by the ANSI committee, appears to forbid it (because it
|blesses cast-away-const for all objects with constructors).

I agree, I really *detest* cast-away-const.  It needs to be thrown away.
Of the alternative proposals, the ~const one seems the best.
[If *any* of the alternativess are blessed, cast-away-const *must* go].
--
sarima@teradata.com   (formerly tdatirv!sarima)
  or
Stanley.Friesen@ElSegundoCA.ncr.com




Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Thu, 1 Oct 1992 16:50:34 GMT
Raw View
In article <1ackm3INNno2@fido.asd.sgi.com> linton@marktwain.rad.sgi.com (Mark Linton) writes:
>This subject has been visited in the past.  There have been several language
>proposals, though I don't know if any are being taken seriously.
>
>Several people missed the basic issue, which is that you have a member function
>that you want to be able to pass a const object (such as a const parameter)
>but the implementation of the function wants to modify member data.
>This happens commonly for cached or lazily-evaluated data, where modifying
>the data doesn't change the future behavior of the object.  The example I typically use
>for this is a matrix object that has an identity() test.  It is easy to imagine that
>
>    We want to call identity() on a const matrix.
>    We don't want to compute the identity test until necessary.
>    We don't want to compute the identity test more than once for the same matrix.
>    We can use access functions to detect changes to the matrix.
>    We don't want a pointer to an extra structure for the matrix representation
>        because we want to create matrices on the stack quickly.
>
>Perhaps most importantly, we shouldn't have to change the specification
>of identity() if we decide to change the implementation (switching between
>non-caching and caching or lazy and non-lazy, for example).
>
>Splitting the class into two classes, one read-only and one read-write doesn't help
>because we really want to call the member function in question on an immutable object

 Spliting into two classes DOES help. IMHO it is cheating
just like casting away const but it works:

 class cache {
 public: int isIdentity;
 };

 class Matrix {
  cache* p;
  int Identity()const { .. actual calculation .. }
  ......
 public:
  .....
  int isIdentity()const
  {
   if(p->isIdentity==-1)p->isIdentity=Identity();
   return p->isIdentity;
  }
 };

>
>There are only two solutions as the language currently stands.  One is
>to cast "this" to a non-const object in the member function.  I agree with
>the original poster that this is ugly, but I suspect others find it
>acceptable enough to classify the whole problem as low priority.
>
>The other solution is not to use const at all.  The downside of this approach
>is that without const you cannot really use ref parameters without risking
>warning messages when you construct temporaries for arguments
>(some compilers are unhappy when you pass a temporary to a non-const ref).
>Without refs it becomes a bit more painful to use objects with natural definitions
>for operations, such as comparisons on strings.

 And the third method is to use a pointer.
--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: aldavi01@starbase.spd.louisville.edu (Arlie Davis)
Date: Fri, 2 Oct 1992 05:37:10 GMT
Raw View
In <1afrmuINN93r@agate.berkeley.edu> jbuck@forney.berkeley.edu (Joe Buck) writes:

> For such objects, "delete" would
> do nothing, since no action is required to free the statically allocated
> memory.  This would only be permissible if operators new and delete
> were not overloaded.

You assume that the constructor and the destructor do nothing but modify
the state of the object.  This is very often not the case.  Many objects
register themselves in a collection of other objects on construction, and
remove themselves from the collection on destruction, or do other such things.

> --
> Joe Buck jbuck@ohm.berkeley.edu
--
lrwx------   1 aldavi01 emacsstu       9 Jun  6 12:43 .signature -> /dev/null




Author: jbuck@forney.berkeley.edu (Joe Buck)
Date: 2 Oct 1992 20:33:51 GMT
Raw View
In <1afrmuINN93r@agate.berkeley.edu> jbuck@forney.berkeley.edu (Joe Buck) writes:
>> For such objects, "delete" would
>> do nothing, since no action is required to free the statically allocated
>> memory.  This would only be permissible if operators new and delete
>> were not overloaded.
>
In article <aldavi01.718004230@starbase.spd.louisville.edu>
aldavi01@starbase.spd.louisville.edu (Arlie Davis) writes:
>You assume that the constructor and the destructor do nothing but modify
>the state of the object.  This is very often not the case.  Many objects
>register themselves in a collection of other objects on construction, and
>remove themselves from the collection on destruction, or do other such things.

I asssume no such thing, as would have been clear if you hadn't lopped off
one short passage from its context.  The shortcuts I propose can only be
done when the complete constructor and destructor bodies are visible at
the point where the object is declared, and there are no side effects like
the ones you speak of.  This usually requires inline constructors and
destructors (or the lack of one or the other).

To make it clearer, consider the following:

class DumbVector {
private:
 double * data;
 int myLength;
public:
 DumbVector(int n) : myLength(n), data(new double[n]) {}
 ~DumbVector() { delete [] data;}
 int length() const { return myLength;}
 double& operator[] (int idx)
 {
  if (idx < 0 || idx >= myLength)
   throw RangeError(*this,idx);
  return data[idx];
 }
};

Now, given the declaration, at file scope:

DumbVector globalVector(1000);

I was pointing out that this object can be initialized at compile time,
by turning it (in a cfront-like implementation) into

struct DumbVector globalVector = { _ptr_somedata, 1000 };
double _ptr_somedata[1000];

and not generating any code to construct or destruct it.

The fact that this cannot be done in general doesn't prevent it from
being done in cases where it is possible.  And there are many more cases
where part, but not all, of the initialization can be done with techniques
such as these.  As is pointed out in John Reiser's paper "Static
Initializers: Reducing the Value-Added Tax on Programs", (from the 1992
Usenix C++ Conference), code to do static initialization of objects has a
large cost in large programs, partly because it tends to page-fault in
the entire application.  The more initializations we can avoid doing at
program startup, the better.  The fact that, as Arlie Davis points out,
we can't do it in every case shouldn't stop us from handling the common
cases where we can.

--
Joe Buck jbuck@ohm.berkeley.edu




Author: brennan@hal.com (Dave Brennan)
Date: 28 Sep 92 18:40:52 GMT
Raw View
I write a lot of C++ classes that are optimized to not do anything any
sooner than they have to.  Here's a contrived example:

class Foo;

class FooList
{
  public:
  FooList ()
    : foo_list (NULL) { }
  // ...
  foo operator [](int);
  // ...

  protected:
  foo **foo_list;

  void create_foo_list ();
};

foo &
foo_list::operator [] (int index)
{
  if (foo_list == NULL)
    create_foo_list ();

  // [ error checking ommited ]

  return (*foo_list[i]);
}


The list of Foo objects doesn't exist until a user of a FooList tries to
access an element of the list.  Conceptually accessing an element is an
operation that I would be like to be able to perform on a const object
because conceptually it does not modify the FooList.

My complaint is that C++ doesn't provide a way to indicate this conceputal
"constness."  I really want to have a const FooList and be able to call a
conceptually const function.  The current alternatives are either don't use
const or do some ugly casting with "this" in conceptually const member
functions.  The first solution makes it harder to insure conceptual const
correctness, while the second is just plain ugly.

Has this problem ever been addressed or pondered before by anyone else?
Is there a better way around the problem?  Is this a problem worthy of a
<*gasp*> language extension?  (A dirty word around here, I know.  Almost
as bad as "new keyword" :-)

--
Dave Brennan                                      HaL Computer Systems
brennan@hal.com                                         (512) 794-2855

Visit the Emacs Lisp Archive: archive.cis.ohio-state.edu:pub/gnu/emacs




Author: frank@Cookie.secapl.com (Frank Adams)
Date: Mon, 28 Sep 1992 21:40:48 GMT
Raw View
In article <BRENNAN.92Sep28124052@yosemite.hal.com> brennan@hal.com (Dave Brennan) writes:
>I write a lot of C++ classes that are optimized to not do anything any
>sooner than they have to.  Here's a contrived example:
>
>[example deleted]
>
>The list of Foo objects doesn't exist until a user of a FooList tries to
>access an element of the list.  Conceptually accessing an element is an
>operation that I would be like to be able to perform on a const object
>because conceptually it does not modify the FooList.
>
>My complaint is that C++ doesn't provide a way to indicate this conceputal
>"constness."  [ugly work-arounds deleted]

One other ugly work-around is to have the body of the class contain only
a pointer to the *real* contents, which you can then modify to your heart's
content.

This indicates another aspect of the problem: a "const" declaration doesn't
force "conceptual constness".  At best (for the reader of a program) it is a
hint that the object is not going to be changed.

>Has this problem ever been addressed or pondered before by anyone else?
>Is there a better way around the problem? Is this a problem worthy of a
><*gasp*> language extension?

This is not a simple problem.  The discussion above only scratched the
surface.  Adding a language extension to deal with this case will only raise
a host of new problems, each (in the new context) at least as urgent as
this.  IMO, it is better to leave good enough alone; get around the problem
by not declaring pointers/references to such classes const.  Leave this kind
of problem for new langauges, which may take a quite different approach to
the whole problem.

Actually, I think that adding "const" to the language was a mistake of just
this sort; this is one amongst the many problems it has given rise to.  This
is 20/20 hindsight, of course.  If I had been the language designer when the
suggestion was made to add it to the language, I would probably have agreed.




Author: pkturner@world.std.com (Prescott K Turner)
Date: Tue, 29 Sep 1992 02:25:17 GMT
Raw View

In article <1992Sep28.214048.117424@Cookie.secapl.com> frank@Cookie.secapl.com (Frank Adams) writes:
> In article <BRENNAN.92Sep28124052@yosemite.hal.com> brennan@hal.com (Dave Brennan) writes:
> >My complaint is that C++ doesn't provide a way to indicate this conceputal
> >"constness."  [ugly work-arounds deleted]
>
> One other ugly work-around is to have the body of the class contain only
> a pointer to the *real* contents, which you can then modify to your heart's
> content.

Another workaround is to have two classes, the conceptually const class
being the base of the regular class.

> >Has this problem ever been addressed or pondered before by anyone else?
> >Is there a better way around the problem? Is this a problem worthy of a
> ><*gasp*> language extension?

There is a proposal before the C++ standards committee, informally called
"~const" for its syntactic characteristic, which addresses the problem.
--
Prescott K. Turner, Jr.
Liant Software Corp. (developers of LPI languages)
959 Concord St., Framingham, MA 01701 USA    (508) 872-8700
UUCP: uunet!lpi!pkt                          Internet: pkt@lpi.liant.com




Author: krc@wam.umd.edu (Kevin R. Coombes)
Date: Tue, 29 Sep 1992 14:07:50 GMT
Raw View
In article <BRENNAN.92Sep28124052@yosemite.hal.com> brennan@hal.com (Dave Brennan) writes:
>I write a lot of C++ classes that are optimized to not do anything any
>sooner than they have to.  Here's a contrived example:
>
>class Foo;
>
>class FooList
>{
>  public:
>  FooList ()
>    : foo_list (NULL) { }
>  // ...
>  foo operator [](int);
>  // ...
>
>  protected:
>  foo **foo_list;
>
>  void create_foo_list ();
>};
>
>foo &
>foo_list::operator [] (int index)
>{
>  if (foo_list == NULL)
>    create_foo_list ();
>
>  // [ error checking ommited ]
>
>  return (*foo_list[i]);
>}
>
>
>The list of Foo objects doesn't exist until a user of a FooList tries to
>access an element of the list.  Conceptually accessing an element is an
>operation that I would be like to be able to perform on a const object
>because conceptually it does not modify the FooList.
>
>My complaint is that C++ doesn't provide a way to indicate this conceputal
>"constness."  I really want to have a const FooList and be able to call a
>conceptually const function.  The current alternatives are either don't use
>const or do some ugly casting with "this" in conceptually const member
>functions.  The first solution makes it harder to insure conceptual const
>correctness, while the second is just plain ugly.
>
>Has this problem ever been addressed or pondered before by anyone else?
>Is there a better way around the problem?  Is this a problem worthy of a
><*gasp*> language extension?  (A dirty word around here, I know.  Almost
>as bad as "new keyword" :-)
>
>--
>Dave Brennan                                      HaL Computer Systems
>brennan@hal.com                                         (512) 794-2855
>
>Visit the Emacs Lisp Archive: archive.cis.ohio-state.edu:pub/gnu/emacs

Although I agree that the difference between "conceptual constness"
and "physical constness" is a real issue in C++, I cannot resist the
impulse to complain about some details of the admittedly contrived
example.

My first complaint is that
 operator[](int index)
should not be a member of a list class. Lists only provide sequential
access; Arrays provide random access.

Now, it is certainly fair to respond to my complaint by changing FooList
into FooArray. I am still not convinced that this particular example
raises the issue of "conceptual constness". If you just want to access
an element of a List or an Array, then should it not just be an error
if the list or array is empty? Why would you want to create an array at
this point?

You have said something about optimization, but I don't see how that applies
here. Perhaps you meant that you wanted to wait to create the collection
until someone tries to insert an element? But inserting an element in a
list is not a conceptually constant operation; the list changes when a
new item is inserted. (Note here that if a list or array is implemented
within the class as a pointer to some region of data, then inserting an
element is a physically const operation on the list pointer.)

In the example at hand, we can avoid the problem by modifying the
design

class FooArray {
 public:
  const Foo &operator[](int index) const;
  void insertAt(int index, Foo f);
};

I'm not about to claim that this is the ideal design. My point is that
fetching an element from an existing array is different from inserting
an element. One source of confusion is that the builtin operator[] can
be used for both operations. The tension in the design results from the
competing impulses: (i) to make operators on user-designed classes match
the builtin operators, and (ii) to enforce conceptual constness.

Finally, let me refer all interested parties to Coplien's book on
Advanced C++, where he provides a design sample which uses an intermediate
FooRef class to distinguish between access (rvalue) and insertion (lvalue)
uses of operator[].

Kevin Coombes <krc@math.umd.edu>




Author: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Date: Tue, 29 Sep 1992 15:29:28 GMT
Raw View
brennan@hal.com (Dave Brennan) writes:

>I write a lot of C++ classes that are optimized to not do anything any
>sooner than they have to.  Here's a contrived example:
>
>class Foo;
>
>class FooList
>{
>  public:
>  FooList ()
>    : foo_list (NULL) { }
>  // ...
>  foo operator [](int);
>  // ...
>
>  protected:
>  foo **foo_list;
>
>  void create_foo_list ();
>};
>
>foo &
>foo_list::operator [] (int index)
>{
>  if (foo_list == NULL)
>    create_foo_list ();
>
>  // [ error checking ommited ]
>
>  return (*foo_list[i]);
>}
>
>
>The list of Foo objects doesn't exist until a user of a FooList tries to
>access an element of the list.  Conceptually accessing an element is an
>operation that I would be like to be able to perform on a const object
>because conceptually it does not modify the FooList.
>
>My complaint is that C++ doesn't provide a way to indicate this conceputal
>"constness."  I really want to have a const FooList and be able to call a
>conceptually const function.  The current alternatives are either don't use
>const or do some ugly casting with "this" in conceptually const member
>functions.  The first solution makes it harder to insure conceptual const
>correctness, while the second is just plain ugly.

One approach which makes your intentions clear is to declare all operations
that are conceptually constant but not actually constant as "conceptual_const":

 #include "conceptual_const.h"
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 >class FooList
 >{
 >  public:
 >  FooList ()
 >    : foo_list (NULL) { }
 >  // ...
    foo operator [](int) conceptual_const;
    ^^^^^^^^^^^^^^^^
 >  // ...
 >
 >  protected:
 >  foo **foo_list;
 >
 >  void create_foo_list ();
 >};

The contents of the "conceptual_const.h" header file are left as an
excercise for the reader ;-)

--
Fergus Henderson             fjh@munta.cs.mu.OZ.AU
This .signature virus is a self-referential statement that is true - but
you will only be able to consistently believe it if you copy it to your own
.signature file! P.S. The above was a *joke*, OK?
--
Fergus Henderson             fjh@munta.cs.mu.OZ.AU
This .signature virus is a self-referential statement that is true - but
you will only be able to consistently believe it if you copy it to your own
.signature file!




Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Tue, 29 Sep 1992 16:04:11 GMT
Raw View
In article <1992Sep29.140750.29438@wam.umd.edu> krc@wam.umd.edu (Kevin R. Coombes) writes:
>In article <BRENNAN.92Sep28124052@yosemite.hal.com> brennan@hal.com (Dave Brennan) writes:
>>sooner than they have to.  Here's a contrived example:
>
>Although I agree that the difference between "conceptual constness"
>and "physical constness" is a real issue in C++, I cannot resist the
>impulse to complain about some details of the admittedly contrived
>example.
>
>My first complaint is that
> operator[](int index)
>should not be a member of a list class. Lists only provide sequential
>access; Arrays provide random access.

 I dont agree really. If you can get the first element,
and get the next element, then you can get the n'th element.
As far as I'm concerned, a list is an array with 'insert' and 'remove'
operations.

 However, it is true that one can have a list *iterator*
than cannot be rewound, and which can only get the next element.
Thus it is list iterators that supply sequential access, not lists
themselves.

--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Tue, 29 Sep 1992 16:10:24 GMT
Raw View
In article <1992Sep29.140750.29438@wam.umd.edu> krc@wam.umd.edu (Kevin R. Coombes) writes:
>In article <BRENNAN.92Sep28124052@yosemite.hal.com> brennan@hal.com (Dave Brennan) writes:
>>I write a lot of C++ classes that are optimized to not do anything any
>>sooner than they have to.  Here's a contrived example:
>>

 Perhaps a better example:

 Matrix A,B,C,D;
 const Matrix X=A*B+C;

Here, X just stores the tree structure A*B+C without evaluating it.
Later, when X has to be used, the actual calculation is done.

Other well known examples (involving phyiscal/logical constness)
involve things like caching, for example
if you have a singly linked list with the [] operator, you
might cache some index/pointer pairs, even for constant lists.

--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: linton@marktwain.rad.sgi.com (Mark Linton)
Date: 30 Sep 1992 16:29:55 GMT
Raw View
This subject has been visited in the past.  There have been several language
proposals, though I don't know if any are being taken seriously.

Several people missed the basic issue, which is that you have a member function
that you want to be able to pass a const object (such as a const parameter)
but the implementation of the function wants to modify member data.
This happens commonly for cached or lazily-evaluated data, where modifying
the data doesn't change the future behavior of the object.  The example I typically use
for this is a matrix object that has an identity() test.  It is easy to imagine that

    We want to call identity() on a const matrix.
    We don't want to compute the identity test until necessary.
    We don't want to compute the identity test more than once for the same matrix.
    We can use access functions to detect changes to the matrix.
    We don't want a pointer to an extra structure for the matrix representation
        because we want to create matrices on the stack quickly.

Perhaps most importantly, we shouldn't have to change the specification
of identity() if we decide to change the implementation (switching between
non-caching and caching or lazy and non-lazy, for example).

Splitting the class into two classes, one read-only and one read-write doesn't help
because we really want to call the member function in question on an immutable object
(the storage changes, but the behavior other than performance does not).

Using a #define conceptual_const doesn't work either.  Again, the problem
is that I have a parameter that I want to declare const and then call the
member function on.  If the function isn't really declared const, then
the compiler will complain if I try to call it on a const object.

There are only two solutions as the language currently stands.  One is
to cast "this" to a non-const object in the member function.  I agree with
the original poster that this is ugly, but I suspect others find it
acceptable enough to classify the whole problem as low priority.

The other solution is not to use const at all.  The downside of this approach
is that without const you cannot really use ref parameters without risking
warning messages when you construct temporaries for arguments
(some compilers are unhappy when you pass a temporary to a non-const ref).
Without refs it becomes a bit more painful to use objects with natural definitions
for operations, such as comparisons on strings.




Author: pete@genghis.borland.com (Pete Becker)
Date: Wed, 30 Sep 1992 17:01:32 GMT
Raw View
In article <1992Sep29.160411.9737@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John MAX Skaller) writes:
>In article <1992Sep29.140750.29438@wam.umd.edu> krc@wam.umd.edu (Kevin R. Coombes) writes:
>>
>>My first complaint is that
>> operator[](int index)
>>should not be a member of a list class. Lists only provide sequential
>>access; Arrays provide random access.
>
> I dont agree really. If you can get the first element,
>and get the next element, then you can get the n'th element.
>As far as I'm concerned, a list is an array with 'insert' and 'remove'
>operations.
>
> However, it is true that one can have a list *iterator*
>than cannot be rewound, and which can only get the next element.
>Thus it is list iterators that supply sequential access, not lists
>themselves.
>

 I take a different view of this.  An array is characterized by indexed
access.  It can be implemented with a list, it can be implemented with a
vector, it can be implemented with a btree, and it can be implemented in many
other ways.  But regardless of the implementation that's chosen, operator[]
is always available, and always has the same semantics.  A program that uses
an array to hold its data will continue to work correctly (neglecting speed
and space considerations) when the underlying implementation of the array
is changed.
 Iteration is really a separate problem.




Author: jbuck@forney.berkeley.edu (Joe Buck)
Date: 30 Sep 1992 23:05:08 GMT
Raw View
In article <1ackm3INNno2@fido.asd.sgi.com> linton@marktwain.rad.sgi.com (Mark Linton) writes:
>This subject has been visited in the past.  There have been several language
>proposals, though I don't know if any are being taken seriously.

I, for one, hope that the ~const proposal is taken seriously.  I'd like
to go even further, and permit objects with constructors to be in ROM
in cases where this is possible (it is possible if the initialization
can be done at compile time and the relevant constructor is inline).
This could already be done, except that the language in the ARM, as
clarified by the ANSI committee, appears to forbid it (because it
blesses cast-away-const for all objects with constructors).

For example, in

class Complex {
 double re, im;
 Complex(double r = 0.0, double i = 0.0) : re(r), im(i) {}
 ...
};

there is no reason why a cfront-style compiler, given the above,
on seeing at file scope

const Complex j(0.0,1.0);

could not produce

const struct Complex j = {0.0,1.0};

The special case I'm talking about is where the constructor is inline,
has no body (nothing in the {}), only member initializations, all the
member initializations are compile-time constant expressions -- things
that could appear in C initializers, that is, and that this rule applies
recursively to any member objects.  I think that the ability to do this
is important in embedded applications.  The ANSI C++ standard should not
mandate this, of course; it just should not prevent it.

>Several people missed the basic issue, which is that you have a member
>function that you want to be able to pass a const object (such as a const
>parameter)
>but the implementation of the function wants to modify member data.

This is already possible without introducing "abstract const" or
"conceptual const".  You do it by including a pointer to non-const
data in the object -- a pointer to the cache.  The cache can then
be changed even in a const object.

class Matrix {
private:
 enum identity_state { unknown, yes, no };

 identity_state *pIdentity;

 // slow version of identity check
 int slowIdentity() const;
 void invalidateCache() { *pIdentity = unknown;}
public:
 Matrix(...args...) : pIdentity(new identity_state) ...
 {
  // other stuff
  invalidateCache();
 }
 int identity() const {
  int status;
  if (*pIdentity == unknown) {
   status = slowIdentity();
   *pIdentity = (status ? yes : no);
  }
  else status = (*pIdentity == yes);
  return status;
 }
};

But then you knew that.  I'm just not impressed with your reasons for
rejecting it.

With the ~const proposal, the objects in the Cache object could be
directly in the ObjWithCachedData object, avoiding the level of
indirection.  However, combining that with the idea of objects in ROM
suggests that const objects with ~const members could be put (partly)
in ROM, by allocating two objects, one in ROM and one in RAM, and putting
a pointer to the RAM part in the ROM part (since the address is fixed,
this could be done in ROM).

>    We don't want to compute the identity test until necessary.

Check.

>    We don't want to compute the identity test more than once for the
same matrix.

Check.

>    We can use access functions to detect changes to the matrix.

This can be provided; functions that change the matrix can call
invalidateCache().

>    We don't want a pointer to an extra structure for the matrix representation
>        because we want to create matrices on the stack quickly.

This is not handled, but a special-purpose allocator could make allocation
of the cache word very fast.

>Perhaps most importantly, we shouldn't have to change the specification
>of identity() if we decide to change the implementation (switching between
>non-caching and caching or lazy and non-lazy, for example).

This is handled; the implementation of identity() could be replaced
with just a call to slowIdentity() without users noticing anything but
a slowdown.

The approach above meets all the criteria except the fourth.  If the
committee accepts ~const, the fourth one is handled as well.

>Splitting the class into two classes, one read-only and one read-write
>doesn't help
>because we really want to call the member function in question on an
>immutable object
>(the storage changes, but the behavior other than performance does not).

But without doing something like the above, how can you assure that you've
done it right?  Once you let programmers cast away const, they proceed to
cast away const with abandon, or else start ignoring warnings about it.
In testing a large program, it helps immeasurably to be able to be able to
rely on the "const promise".  With the current state of the language, I
think that the extra level of indirection is the best way to go.  If this
violates your concern about creating matrices on the stack quickly, create
a special-purpose allocator -- pool allocators that hand out fixed-size
blocks can be much faster than general "malloc-style" allocators.


--
Joe Buck jbuck@ohm.berkeley.edu




Author: pkturner@world.std.com (Prescott K Turner)
Date: 1 Oct 92 02:06:58 GMT
Raw View
In article <1ackm3INNno2@fido.asd.sgi.com> Mark Linton writes:
> Several people missed the basic issue, which is that you have a member function
> that you want to be able to pass a const object (such as a const parameter)
> but the implementation of the function wants to modify member data.
> This happens commonly for cached or lazily-evaluated data, where modifying
> the data doesn't change the future behavior of the object.  The example I typica
> lly use
> for this is a matrix object that has an identity() test.
> [...]
> Perhaps most importantly, we shouldn't have to change the specification
> of identity() if we decide to change the implementation (switching between
> non-caching and caching or lazy and non-lazy, for example).
>
> Splitting the class into two classes, one read-only and one read-write doesn't help
> because we really want to call the member function in question on an immutable object
> (the storage changes, but the behavior other than performance does not).

I don't think you grasped my workaround.  See my second article
<BvDxMo.5yu@world.std.com>.  You want to call the member function on a
conceptually immutable object.  The two-class workaround does not use the
existing 'const' keyword/mechanism at all.  The 'identity' or whatever
would be a public member function of the const_matrix base class.
An "immutable object" would be declared as a const_matrix, and so could have
'identity' called for it.  Since const_matrix::identity is not declared
with the const keyword, caching or laziness can be used in the implementation.

 class const_matrix {
 public:
  int identity ();
  ...
 };
 class matrix : public const_matrix ();
 public:
  matrix & operator = (const_matrix &);
  ...
 };
 // Declare a conceptually immutable matrix.
 const_matrix conceptually_immutable = ...
 int is_ident = conceptually_immutable.identity();

I wouldn't call this a solution, only a workaround.  And for most purposes
I prefer the indirection approach described by Joe Buck, because the
interfaces are more what people expect.
--
Prescott K. Turner, Jr.
Liant Software Corp. (developers of LPI languages)
959 Concord St., Framingham, MA 01701 USA    (508) 872-8700
UUCP: uunet!lpi!pkt                          Internet: pkt@lpi.liant.com




Author: pat@frumious.uucp (Patrick Smith)
Date: Thu, 1 Oct 1992 05:32:36 GMT
Raw View
jbuck@forney.berkeley.edu (Joe Buck) writes:
|I, for one, hope that the ~const proposal is taken seriously.  I'd like
|to go even further, and permit objects with constructors to be in ROM
|in cases where this is possible (it is possible if the initialization
|can be done at compile time and the relevant constructor is inline).

Const objects are treated as non-const inside destructors.

So another necessary condition is that either the object's destructor
is never called or it does not change the object.

--
Patrick Smith
uunet.ca!frumious!pat
pat%frumious.uucp@uunet.ca