Topic: Type System


Author: pat@frumious.uucp (Patrick Smith)
Date: Thu, 14 Jan 1993 18:38:33 GMT
Raw View
Jerry Schwarz writes:
| If an lvalue has non-const type,
| then it refers to a mutable object.

Patrick Smith writes:
| There is another loophole. :-(

Jerry Schwarz writes:
|If anyone has ideas for a clean way
|to close this loophole, I'd be interested to hear them.

Jim Adcock writes:
|A* pa;
|
|A::A() const
|{
| pa = this; // compile-time error: non-const ptr to const object
|}
|
|Such a const ctor can only give its members or base parts values via
|the colon section initializers.  Members cannot be changed within the
|body of the constructor.  For backwards compatibility, if you don't
|declare a const ctor, then the loophole would be allowed to continue
|in ctors of that class, and in which case members could continue to
|be changed within the body of the ctor.


A nice idea, but...

Should it be possible for a const constructor to use non-const
constructors to initialize bases and members?

If no, it becomes impossible to create a const constructor for
some classes:

   class A { public: A(); /* no const constructor */ };
   class B : pubic A { public: B() const; };
   B::B() const {}   // Error: uses non-const A::A()
                     // There is _no_ legal way to
                     // implement B::B() const!

If yes, then one can still violate constness for a class with
a const constructor, if that class has a base class with
no const constructor.

   class B;

   class A {
      A* p;
   public:
      A() { p = this; }
      virtual B* f() { return 0; }
      B* g() const { return p->f(); }
   };

   class B : public A {
   public:
      B() const : A() {}
      virtual B* f() { return this; }
   };

   void foo() {
      const B b;
      // b->g() is a plain B* but points to b, which is const
   }

This can also be done without the virtual function, by explicitly
casting an A* to a B*, but without violating constness in the cast.


Neither alternative makes the idea useless, but they do make it
less useful.  Or is there some way around this that I haven't
thought of?

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




Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Mon, 18 Jan 1993 16:03:34 GMT
Raw View
In article <C0ux4A.C9@frumious.uucp> uunet.ca!frumious!pat writes:
>
>Jim Adcock writes:
>|
>|A::A() const
>|{
>| pa = this; // compile-time error: non-const ptr to const object
>|}
>|
>|Such a const ctor can only give its members or base parts values via
>|the colon section initializers.  Members cannot be changed within the
>|body of the constructor.  For backwards compatibility, if you don't
>|declare a const ctor, then the loophole would be allowed to continue
>|in ctors of that class, and in which case members could continue to
>|be changed within the body of the ctor.
>
>
>A nice idea, but...
>
>Should it be possible for a const constructor to use non-const
>constructors to initialize bases and members?

 No.
>
>If no, it becomes impossible to create a const constructor for
>some classes:
>
>   class A { public: A(); /* no const constructor */ };
>   class B : pubic A { public: B() const; };
>   B::B() const {}   // Error: uses non-const A::A()
>                     // There is _no_ legal way to
>                     // implement B::B() const!

 So what? Make all the ctors 'const'.

 This is really the problem: sometimes it isnt possible.
(Example still required: anyone?)

>Neither alternative makes the idea useless, but they do make it
>less useful.  Or is there some way around this that I haven't
>thought of?

 We could just decide all existing constructors
we 'const' anyhow. That would fix the loophole without
changing anything.

 How many assigments in your constructors could be
changed to initialisations? All of them? Calls to
non-const members functions?

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




Author: pat@frumious.uucp (Patrick Smith)
Date: Tue, 19 Jan 1993 07:48:56 GMT
Raw View
maxtal@extro.ucc.su.OZ.AU (John MAX Skaller) writes:
|In article <C0ux4A.C9@frumious.uucp> uunet.ca!frumious!pat writes:
|>   class A { public: A(); /* no const constructor */ };
|>   class B : pubic A { public: B() const; };
|>   B::B() const {}   // Error: uses non-const A::A()
|>                     // There is _no_ legal way to
|>                     // implement B::B() const!
|
| So what? Make all the ctors 'const'.

In the example above, I was assuming one wasn't able to modify
the base class A.  So you're stuck with it not having a const
constructor.


| This is really the problem: sometimes it isnt possible.
|(Example still required: anyone?)

I'm not sure, but I suspect that if one is allowed to modify
the base and member classes, then it is always possible
(barring unusual constraints posed by the application).
It might occasionally be quite awkward, though.

But if you can't modify the base and member classes, then it's
easy to construct examples where it's impossible.  For example,
a base class might not have a const constructor, as above.
Or a constructor may need to put a member into a state which
can only be achieved by calling non-const member functions for
the member object.


| We could just decide all existing constructors
|we 'const' anyhow. That would fix the loophole without
|changing anything.

I don't understand this.  Could you explain this in more detail?
(Maybe in e-mail if I'm missing something obvious.)

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




Author: jss@lucid.com (Jerry Schwarz)
Date: Sat, 9 Jan 93 00:19:48 GMT
Raw View
In article <C0Iy9o.9x@frumious.uucp>, pat@frumious.uucp (Patrick Smith) writes:
|> jss@lucid.com (Jerry Schwarz) writes:
|> |The way I understand it, when an object is created (constructed)
|> |it can be "marked" as mutable or immutable. ("Marked" in quotes
|> |because no runtime marking is implied).  This mark cannot be
|> |changed.  The type system is designed so that (barring the
|> |use of unsafe casts) the following rule is assured.
|> |
|> | If an lvalue has non-const type,
|> | then it refers to a mutable object.
|>
|> There is another loophole. :-(
|> But it's (I hope) an unusual type of situation. :-)
|>
|>    struct A { A(); /*...*/ };
|>    A* pa;
|>    A::A { pa = this; }
|>
|>    void f() {
|>       const A a;
|>       // Now pa has non-const type but points to an immutable object.
|>    }

You're right.  I knew about that loophole, but forgot about it when
I posted my original item.  If anyone has ideas for a clean way
to close this loophole, I'd be interested to hear them.

The problem is that the object is only "marked" immutable when the
constructor returns.  The constructor itself is supposed to be free
to modify the object in any way it chooses.

  -- Jerry Schwarz





Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Sun, 10 Jan 1993 16:27:57 GMT
Raw View
In article <1993Jan9.001948.28388@lucid.com> jss@lucid.com (Jerry Schwarz) writes:
>In article <C0Iy9o.9x@frumious.uucp>, pat@frumious.uucp (Patrick Smith) writes:
>|> There is another loophole. :-(
>|> But it's (I hope) an unusual type of situation. :-)

 Special case of a more general problem?

>|>
>|>    struct A { A(); /*...*/ };
>|>    A* pa;
>|>    A::A { pa = this; }
>|>
>|>    void f() {
>|>       const A a;
>|>       // Now pa has non-const type but points to an immutable object.
>|>    }

 LOVELY! Who needs ~const?  Who needs cast away const either?

 struct A { A*me; A() : me(this) {}
  int i;
  f()const;
 }

 A::f()const { ....
  me->i=1; // fooled ya!
 };

>
>You're right.  I knew about that loophole, but forgot about it when
>I posted my original item.  If anyone has ideas for a clean way
>to close this loophole, I'd be interested to hear them.

 Do we want to close it?
>
>The problem is that the object is only "marked" immutable when the
>constructor returns.  The constructor itself is supposed to be free
>to modify the object in any way it chooses.

 The problem is that the object is *never* marked const.
What is made const is the 'reference' or pointer to the object.

 If we had 'const' constructors we could at least
distinguish whether a const object was being constructed or not.
Please take below with salt (LOTS):

 You could close the loophole by disallowing any use
of 'this' :-)

 This also stops the constructor creating another
object and passing it the non-const this.

 I'm not sure if the loophole matters though. The
const mechanism is an aid only. It totally fails to
handle 'delegation': it is a well known trick for cache
support etc:

 class X { int i; .. };
 class H { X* p; H() p(new X){}};
 const H h; // nothing can help us: the constructor of H
     // doesnt know its const
    // so it cant create a 'const' X object

 h.p->i=2; // modified the 'object'

To get around this

 class constH {const X* p; constH() : p(new const X){}};
 constH h;
 h.p->i=1; //error

which means whenever you want to support 'const' and 'delegation'
like this you have to create two 'handle' classes : a const one,
and a non-const one. (This doesnt close the loophole, just
lets you create const objects that *subsequently* cant
be modified).

I'm not sure even having 'const' constructors would help here.
(Set a switch?)

Now: the loophole is just a special case of this in which
the pointer *happens* to refer to the object itself.

So it isnt a loophole unless the delegation thing above
is also. The user code is just delegating control to itself :-)

Moral: there's no loophole: your notion that there exist
const objects is faulty. Only references and pointers
to objects can be 'const'. Its up to the constructor
to determine if it wants to give certain objects
non-const access to the object by passing them
the 'this' pointer.

General public access is still restricted as intended.
Priviledged access is still available to those
trusted objects that the constructor deems to
grant non-const priviledge to (including the object
itself --- as a special case).

Now consider a REAL const object:

 rom X x;

Thats really in ROM. Now: the constructor had better
NOT modify the object: its REALLY immutable!

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




Author: jss@lucid.com (Jerry Schwarz)
Date: Mon, 11 Jan 93 23:34:27 GMT
Raw View
In article <1993Jan10.162757.1097@ucc.su.OZ.AU>, maxtal@extro.ucc.su.OZ.AU (John MAX Skaller) writes:


jss:
|> >You're right.  I knew about that loophole, but forgot about it when
|> >I posted my original item.  If anyone has ideas for a clean way
|> >to close this loophole, I'd be interested to hear them.
|>

maxtal:
|>  Do we want to close it?


I do.

jss:
|> >
|> >The problem is that the object is only "marked" immutable when the
|> >constructor returns.  The constructor itself is supposed to be free
|> >to modify the object in any way it chooses.

maxtal:
|>
|>  The problem is that the object is *never* marked const.
|> What is made const is the 'reference' or pointer to the object.

The original item explained that "marking" wasn't actually caried
out a runtime, but the concept is useful in explaining the intentions
of the type system.

To repeat. The desired (by me) property (with slightly more
explicit wording than the original and avoiding the use of "mark's")
is

 If an object is constructed const(immutable), then
 between the time that the constructor (if any) completes
        and the destructor (if any) is called (if ever) the
        type system should guarantee that any expression that refers to
        this object or a subobject of it will do so via
        const(qualified) type.

I use const(immutable) to indicate a property of an object, and
const(qualified) to indicate a property of a type.

This is a desirable property whether or not "const" has any effect
on the primitives, and whether or not the language specifies the
effect of modifying a const(immutable) object.

Anything that permits violation of this property is a "loophole".

The rest of maxtal's discussion concerned the meaning of "const",
which is a different subject altogether.

   -- Jerry Schwarz




Author: jimad@microsoft.com (Jim Adcock)
Date: 12 Jan 93 21:45:47 GMT
Raw View
In article <1993Jan9.001948.28388@lucid.com> jss@lucid.com (Jerry Schwarz) writes:
>In article <C0Iy9o.9x@frumious.uucp>, pat@frumious.uucp (Patrick Smith) writes:
||> | If an lvalue has non-const type,
||> | then it refers to a mutable object.
||>
||> There is another loophole. :-(
||> But it's (I hope) an unusual type of situation. :-)
||>
||>    struct A { A(); /*...*/ };
||>    A* pa;
||>    A::A { pa = this; }
||>
||>    void f() {
||>       const A a;
||>       // Now pa has non-const type but points to an immutable object.
||>    }
|
|You're right.  I knew about that loophole, but forgot about it when
|I posted my original item.  If anyone has ideas for a clean way
|to close this loophole, I'd be interested to hear them.

A* pa;

A::A() const
{
 pa = this; // compile-time error: non-const ptr to const object
}

Such a const ctor can only give its members or base parts values via
the colon section initializers.  Members cannot be changed within the
body of the constructor.  For backwards compatibility, if you don't
declare a const ctor, then the loophole would be allowed to continue
in ctors of that class, and in which case members could continue to
be changed within the body of the ctor.




Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Wed, 13 Jan 1993 18:23:15 GMT
Raw View
In article <1993Jan12.214547.20528@microsoft.com> jimad@microsoft.com (Jim Adcock) writes:
>
>A* pa;
>
>A::A() const
>
>Such a const ctor can only give its members or base parts values via
>the colon section initializers.  Members cannot be changed within the
>body of the constructor.

 Implies that all the base classes and members ought
to have const constructors too.

>For backwards compatibility, if you don't
>declare a const ctor, then the loophole would be allowed to continue
>in ctors of that class, and in which case members could continue to
>be changed within the body of the ctor.

 Not just for backwards compatibility. For necessity?
Many times I've needed (or thought I needed)
to put things in the body. Example anyone?

(Cant think of one: maybe const constructors are viable.
In which case .. why not make them ALL const :-)

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




Author: jss@lucid.com (Jerry Schwarz)
Date: Mon, 4 Jan 93 19:43:18 GMT
Raw View
In article <1993Jan1.172044.9659@ucc.su.OZ.AU>, maxtal@extro.ucc.su.OZ.AU (John MAX Skaller) writes:
|> Further to a previous post describing modifications to the type
|> system, I want to try to explain the underlying concept with some
|> further examples.
|>
|> The basic idea is that 'int' and 'T' are object types,
|> whereas 'int&' or 'const int&' are not object types.
|> I would say they are 'accessors' to an object of type 'int'.

The semantics of "const" in types is confused because it is also
unsed with a different meaning in the English descriptions of
C++.  For several years I have been using "immutable" for this
sense of "const".  That adjective hasn't yet caught on but,
I live in hope.

The way I understand it, when an object is created (constructed)
it can be "marked" as mutable or immutable. ("Marked" in quotes
because no runtime marking is implied).  This mark cannot be
changed.  The type system is designed so that (barring the
use of unsafe casts) the following rule is assured.

 If an lvalue has non-const type,
 then it refers to a mutable object.

Thus |int&| and |const int&| differ precisely because they
refer to objects rather than values.  The values that these
objects can hold are the same, but the information we have
about the setting of the mutability flag is different.

Discussions of "const" in the type system become slippery because
when talking about values mutability is irrelevant and so "int"
and "const int" are in some sense the same.

  -- Jerry Schwarz








Author: pat@frumious.uucp (Patrick Smith)
Date: Fri, 8 Jan 1993 07:32:11 GMT
Raw View
jss@lucid.com (Jerry Schwarz) writes:
|The way I understand it, when an object is created (constructed)
|it can be "marked" as mutable or immutable. ("Marked" in quotes
|because no runtime marking is implied).  This mark cannot be
|changed.  The type system is designed so that (barring the
|use of unsafe casts) the following rule is assured.
|
| If an lvalue has non-const type,
| then it refers to a mutable object.

There is another loophole. :-(
But it's (I hope) an unusual type of situation. :-)

   struct A { A(); /*...*/ };
   A* pa;
   A::A { pa = this; }

   void f() {
      const A a;
      // Now pa has non-const type but points to an immutable object.
   }

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




Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Fri, 1 Jan 1993 17:20:44 GMT
Raw View
Further to a previous post describing modifications to the type
system, I want to try to explain the underlying concept with some
further examples.

The basic idea is that 'int' and 'T' are object types,
whereas 'int&' or 'const int&' are not object types.
I would say they are 'accessors' to an object of type 'int'.

All identifiers (and expressions) are accessors.
Consider:

 int x;
 int& y=x;
 const int& z=x;

In use, one cannot distinguish 'x' and 'y'. It is absurd IMHO
to pretend that 'x' is of type 'int' whereas 'y' is of type 'int&'.
Instead, one says that 'x' and 'y' and indeed 'z' refer to,
or access, an object of type int.

The 'access' method of both 'x' and 'y' is reference to int,
i.e. they are both 'int&'. 'z' is also a reference to an
int, but it has a restriction imposed preventing the call
of non-const functions.

Consequences: of this and the previous post:

consider a template function:

template<class T> f(T){ ... }

what function is generated  by the calls

 f(3);
 f(x);
 f(y);
 f(z);
??

My system is simple: in all cases

 f(int);

is used. If you wanted to create

 f(int&);

you should have defined

template<class T> f(T&) { ...}

(in which case the calls f(3), f(z) are illegal)

The reason is trivial: <class T> stands for a class (or pointer)
type, ie an object type, and int& is not a type, but a reference
to an object of the type int.

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