From: jbuck@forney.berkeley.edu (Joe Buck)
Newsgroups: comp.lang.c++,comp.std.c++
Subject: Re: Conceptual Constness
Date: 30 Sep 1992 23:05:08 GMT
Organization: U. C. Berkeley
Distribution: world
NNTP-Posting-Host: forney.berkeley.edu
Keywords: const, const member, lazy

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