www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - mutable constant?

reply "Namespace" <rswhite4 googlemail.com> writes:
I want to ask if this code should compile or if it's a bug, 
because I circumvent the const system:

----
import std.stdio;

struct Point {
	int x, y;
}

Point*[] points;

struct TplPoint(T) {
public:
	Point _point;

	T x, y;

	const uint id;

	this(T x, T y) {
		this.x = x;
		this.y = y;

		points ~= &this._point;

		id = points.length - 1;
	}

	 property
	inout(Point)* ptr() inout {
		points[this.id].x = cast(int) this.x;
		points[this.id].y = cast(int) this.y;

		return cast(inout Point*) points[this.id];
	}
}

void main() {
	const TplPoint!float my = TplPoint!float(42, 23);
	writeln(my._point, "::", &my._point);
	writeln(*my.ptr, "::", my.ptr);
}
----

Or is the fact that it compiles ok and it's "only" unsafe?
Jun 25 2013
next sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On Wed, 26 Jun 2013 00:07:38 +0200, Namespace <rswhite4 googlemail.com>  
wrote:

 I want to ask if this code should compile or if it's a bug, because I  
 circumvent the const system:

 ----
 import std.stdio;

 struct Point {
 	int x, y;
 }

 Point*[] points;

 struct TplPoint(T) {
 public:
 	Point _point;

 	T x, y;

 	const uint id;

 	this(T x, T y) {
 		this.x = x;
 		this.y = y;

 		points ~= &this._point;

 		id = points.length - 1;
 	}

 	 property
 	inout(Point)* ptr() inout {
 		points[this.id].x = cast(int) this.x;
 		points[this.id].y = cast(int) this.y;

 		return cast(inout Point*) points[this.id];
 	}
 }

 void main() {
 	const TplPoint!float my = TplPoint!float(42, 23);
 	writeln(my._point, "::", &my._point);
 	writeln(*my.ptr, "::", my.ptr);
 }
 ----

 Or is the fact that it compiles ok and it's "only" unsafe?
This is perfectly fine. -- Simen
Jun 25 2013
prev sibling next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Wednesday, June 26, 2013 00:07:38 Namespace wrote:
 I want to ask if this code should compile or if it's a bug,
 because I circumvent the const system:
 
 ----
 import std.stdio;
 
 struct Point {
 int x, y;
 }
 
 Point*[] points;
 
 struct TplPoint(T) {
 public:
 Point _point;
 
 T x, y;
 
 const uint id;
 
 this(T x, T y) {
 this.x = x;
 this.y = y;
 
 points ~= &this._point;
 
 id = points.length - 1;
 }
 
  property
 inout(Point)* ptr() inout {
 points[this.id].x = cast(int) this.x;
 points[this.id].y = cast(int) this.y;
 
 return cast(inout Point*) points[this.id];
 }
 }
 
 void main() {
 const TplPoint!float my = TplPoint!float(42, 23);
 writeln(my._point, "::", &my._point);
 writeln(*my.ptr, "::", my.ptr);
 }
 ----
 
 Or is the fact that it compiles ok and it's "only" unsafe?
I could certainly be missing something here, but I don't understand what about the code you're even concerned about. Where in here would you be breaking the type system? I don't see any place in here where you're mutating a const variable or anything like that. The worst thing I see about the code is that it won't compile on 64-bit machines thanks to id = points.length - 1; - Jonathan M Davis
Jun 25 2013
parent reply "Namespace" <rswhite4 googlemail.com> writes:
If you change uint to size_t it works fine AFAIK.
Jun 25 2013
parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Wednesday, June 26, 2013 00:52:58 Namespace wrote:
 If you change uint to size_t it works fine AFAIK.
Yes. It's easy enough to fix, but it _is_ arguably a bug in the code, and it's the only one that's obvious to me. I don't understand what about the code makes you think that it might be violating the type system. - Jonathan M Davis P.S. Please always quote at least a portion of the post that you're replying to. Clients frequently do not manage to thread posts correctly, and it's not always clear which post a post is replying to if it doesn't quote any of its parent. In this particular case, based on what you said, I expect that you were replying to me, but it's threaded in my client as being a responsed to Simen. Also, not all posters even use a threaded view. So, relying on threading to make it clear which post you're replying to is not a good idea.
Jun 25 2013
prev sibling parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
With apologies, I have unrelated comments to make.

On 06/25/2013 03:07 PM, Namespace wrote:

      this(T x, T y) {
          this.x = x;
          this.y = y;

          points ~= &this._point;
I have seen similar designs in the past where constructors had side-effects such as registering the object in a global state. (Exactly what the last line is doing above.) It has almost always been cause of trouble. It is better to register an object from the outside after constructing it. Sometimes I had attempted to remove seemingly unused objects only to be reminded by a comment that it should not be: // Do not remove! Registers itself in the points array auto p = Point();
       property
      inout(Point)* ptr() inout {
          points[this.id].x = cast(int) this.x;
          points[this.id].y = cast(int) this.y;
That looks questionable as well: ptr() looks like an accessor but it makes changes to a global state. Ali
Jun 25 2013
parent reply "Namespace" <rswhite4 googlemail.com> writes:
On Tuesday, 25 June 2013 at 23:39:45 UTC, Ali Çehreli wrote:
 With apologies, I have unrelated comments to make.

 On 06/25/2013 03:07 PM, Namespace wrote:

      this(T x, T y) {
          this.x = x;
          this.y = y;

          points ~= &this._point;
I have seen similar designs in the past where constructors had side-effects such as registering the object in a global state. (Exactly what the last line is doing above.) It has almost always been cause of trouble. It is better to register an object from the outside after constructing it. Sometimes I had attempted to remove seemingly unused objects only to be reminded by a comment that it should not be: // Do not remove! Registers itself in the points array auto p = Point();
       property
      inout(Point)* ptr() inout {
          points[this.id].x = cast(int) this.x;
          points[this.id].y = cast(int) this.y;
That looks questionable as well: ptr() looks like an accessor but it makes changes to a global state. Ali
This is no real code. Just a test example to check. ;)
Jun 26 2013
parent reply "monarch_dodra" <monarchdodra gmail.com> writes:
On Wednesday, 26 June 2013 at 07:35:08 UTC, Namespace wrote:
 On Tuesday, 25 June 2013 at 23:39:45 UTC, Ali Çehreli wrote:
 With apologies, I have unrelated comments to make.

 On 06/25/2013 03:07 PM, Namespace wrote:

     this(T x, T y) {
         this.x = x;
         this.y = y;

         points ~= &this._point;
I have seen similar designs in the past where constructors had side-effects such as registering the object in a global state. (Exactly what the last line is doing above.) It has almost always been cause of trouble. It is better to register an object from the outside after constructing it. Sometimes I had attempted to remove seemingly unused objects only to be reminded by a comment that it should not be: // Do not remove! Registers itself in the points array auto p = Point();
      property
     inout(Point)* ptr() inout {
         points[this.id].x = cast(int) this.x;
         points[this.id].y = cast(int) this.y;
That looks questionable as well: ptr() looks like an accessor but it makes changes to a global state. Ali
This is no real code. Just a test example to check. ;)
It seems safe, however, your example seems to show how to indeed break the type system... without a cast (!): property Point* ptr() inout { points[this.id].x = cast(int) this.x; points[this.id].y = cast(int) this.y; return points[this.id]; } void main() { immutable TplPoint!float my = TplPoint!float(42, 23); Point* p = my.ptr; //Oops! mutable point! } Disturbing...
     this(T x, T y) {
         this.x = x;
         this.y = y;

         points ~= &this._point;
I'd careful with this, you can easily end up with pointers to destroyed temporaries...
Jun 26 2013
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, June 26, 2013 13:16:16 monarch_dodra wrote:
 It seems safe, however, your example seems to show how to indeed
 break the type system... without a cast (!):
 
       property
      Point* ptr() inout {
          points[this.id].x = cast(int) this.x;
          points[this.id].y = cast(int) this.y;
 
          return points[this.id];
      }
 
 
 void main() {
      immutable TplPoint!float my = TplPoint!float(42, 23);
      Point* p = my.ptr; //Oops! mutable point!
 }
 
 Disturbing...
It doesn't break anything. It just shows the need for pure. - Jonathan M Davis
Jun 26 2013
next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Wednesday, 26 June 2013 at 15:48:42 UTC, Jonathan M Davis 
wrote:
 It doesn't break anything. It just shows the need for pure.

 - Jonathan M Davis
OOhhhh.... I just got it :( nevermind then...
Jun 26 2013
prev sibling parent reply "anonymous" <anonymous example.com> writes:
On Wednesday, 26 June 2013 at 15:48:42 UTC, Jonathan M Davis 
wrote:
 It doesn't break anything. It just shows the need for pure.
Really? In the following simplified code I see mutation of an immutable variable, which should not be possible, of course. That is breaking the type system, no? What am I missing? import std.stdio; int* point; struct TplPoint { int _point; this(int x) { _point = x; point = &_point; } } void main() { immutable TplPoint my = TplPoint(42); writeln(my._point); // 42 *point = 13; // uh-oh writeln(my._point); // 13 !!! }
Jun 26 2013
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Thursday, June 27, 2013 01:45:22 anonymous wrote:
 On Wednesday, 26 June 2013 at 15:48:42 UTC, Jonathan M Davis
 
 wrote:
 It doesn't break anything. It just shows the need for pure.
Really? In the following simplified code I see mutation of an immutable variable, which should not be possible, of course. That is breaking the type system, no? What am I missing? import std.stdio; int* point; struct TplPoint { int _point; this(int x) { _point = x; point = &_point; } } void main() { immutable TplPoint my = TplPoint(42); writeln(my._point); // 42 *point = 13; // uh-oh writeln(my._point); // 13 !!! }
It looks to me like your code is fundamentally different from the OP's example rather than being a simplification of the original code. In the OP's example, the variable being mutated is a module-level variable, so the immutability of the object is irrelevant when its member function is called (since it's not the object itself which is being mutated). It also has nothing to do with construction. Your example, on the other hand, is showing a bug with regards to constructing immutable objects in that the object doesn't actually become immutable until it's fully constructed, and the compiler isn't catching something which then violates the impending immutability. I _think_ that that bug has already been reported, but I can't find it at the moment. But regardless, your example is quite different from the OP's example. - Jonathan M Davis
Jun 26 2013
parent reply "anonymous" <anonymous example.com> writes:
On Thursday, 27 June 2013 at 00:53:48 UTC, Jonathan M Davis wrote:
 It looks to me like your code is fundamentally different from 
 the OP's example
 rather than being a simplification of the original code. In the 
 OP's example,
 the variable being mutated is a module-level variable, so the 
 immutability of
 the object is irrelevant when its member function is called 
 (since it's not
 the object itself which is being mutated). It also has nothing 
 to do with
 construction.

 Your example, on the other hand, is showing a bug with regards 
 to constructing
 immutable objects in that the object doesn't actually become 
 immutable until
 it's fully constructed, and the compiler isn't catching 
 something which then
 violates the impending immutability.
I don't see the fundamental difference. In both versions: - An immutable struct instance is constructed. mine: immutable TplPoint my = TplPoint(42); OP: const TplPoint!float my = TplPoint!float(42, 23); Note that this does not set my._point to Point(42, 23). - The constructor stores a mutable pointer to its contents in module scope. mine: point = &_point; OP: points ~= &this._point; - Via that mutable pointer the data is altered. mine: *point = 13; OP: a bit more convoluted, TplPoint.ptr does the nasty work, but it could just as well be in main: points[someid].x = somevalue; => Immutability broken.
Jun 26 2013
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Thursday, June 27, 2013 03:31:01 anonymous wrote:
 On Thursday, 27 June 2013 at 00:53:48 UTC, Jonathan M Davis wrote:
 It looks to me like your code is fundamentally different from
 the OP's example
 rather than being a simplification of the original code. In the
 OP's example,
 the variable being mutated is a module-level variable, so the
 immutability of
 the object is irrelevant when its member function is called
 (since it's not
 the object itself which is being mutated). It also has nothing
 to do with
 construction.
 
 Your example, on the other hand, is showing a bug with regards
 to constructing
 immutable objects in that the object doesn't actually become
 immutable until
 it's fully constructed, and the compiler isn't catching
 something which then
 violates the impending immutability.
I don't see the fundamental difference. In both versions: - An immutable struct instance is constructed. mine: immutable TplPoint my = TplPoint(42); OP: const TplPoint!float my = TplPoint!float(42, 23); Note that this does not set my._point to Point(42, 23). - The constructor stores a mutable pointer to its contents in module scope. mine: point = &_point; OP: points ~= &this._point; - Via that mutable pointer the data is altered. mine: *point = 13; OP: a bit more convoluted, TplPoint.ptr does the nasty work, but it could just as well be in main: points[someid].x = somevalue; => Immutability broken.
You're right. I didn't read over the OP's example carefully enough. The mutation is being done to a module-level variable in an inout function, which is completely legit. I thought that what the OP thought was wrong was mutating a module-level variable in a non-mutable function (and that's perfectly fine as long as it's not pure). What I missed (and you didn't) was the fact that that module-level variable was pointing to the contents of the object which was const. And that mutable pointer _is_ being obtained via the constructor just like in your example. And that is most definitely a compiler bug - the same one that your example shows. - Jonathan M Davis
Jun 26 2013
parent "Namespace" <rswhite4 googlemail.com> writes:
 You're right. I didn't read over the OP's example carefully 
 enough. The
 mutation is being done to a module-level variable in an inout 
 function, which
 is completely legit. I thought that what the OP thought was 
 wrong was mutating
 a module-level variable in a non-mutable function (and that's 
 perfectly fine as
 long as it's not pure). What I missed (and you didn't) was the 
 fact that that
 module-level variable was pointing to the contents of the 
 object which was
 const. And that mutable pointer _is_ being obtained via the 
 constructor just
 like in your example. And that is most definitely a compiler 
 bug - the same one
 that your example shows.

 - Jonathan M Davis
That was exactly my question.
Jun 26 2013