www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Abnormal struct properties behaviour

reply nail <nail_member pathlink.com> writes:
Hi all.

Consider following situation. We have a struct named Vector3 it has members
public x, y and z. Also we have a class Camera that has property, for example,
eye. The code is something like this:

struct Vector3
{
float x, y, z;
...
}

class Camera
{
protected Vector3 _eye;

public void eye(Vector3 v)
{
_eye = v;
// do something more
}

public Vector3 eye()
{
return _eye;
}
}

void main()
{
Camera cam = new Camera();
cam.eye.y = 5; // dead line
}

In the code above "dead line" leads to changing y of the copy of the eye. I
think this is not desired behaviour. Something must be done to correct this
language behaviour. I don't know what is the best solution, but the following
IMHO should work:
If we write-access members/props of the struct returned by property then for
writing second, write-property, must be called with parameter that is the
changed copy of the read-property. ahem... I think I explain too complicately
:). In another words, in the example above:
1) cam.eye() returns a copy of original vector _eye
2) 'y' component of this copy is changed to 5
3) write-property came.eye(Vector3) must be called with parameter obtained in
previous action.

Comments?
Dec 20 2004
next sibling parent "Lionello Lunesu" <lionello.lunesu crystalinter.remove.com> writes:
Hi.

"nail" <nail_member pathlink.com> wrote in message 
news:cq6h3c$1tj$1 digitaldaemon.com...

 [...]
 public Vector3 eye()
 {
 return _eye;
 }
 }

 void main()
 {
 Camera cam = new Camera();
 cam.eye.y = 5; // dead line
 }

 In the code above "dead line" leads to changing y of the copy of the eye.

Check the other thread in this newsgroup about "inout return values". I guess you'd want something like "inout Vector3 eye() { return eye; }" which would return a C++-like reference to the struct, instead of a copy. ....This is not yet possible, by the way :-S L.
Dec 20 2004
prev sibling next sibling parent reply =?ISO-8859-1?Q?Anders_F_Bj=F6rklund?= <afb algonet.se> writes:
nail wrote:

 Consider following situation. We have a struct named Vector3 it has members
 public x, y and z. Also we have a class Camera that has property, for example,
 eye. 
 
 In the code above "dead line" leads to changing y of the copy of the eye. I
 think this is not desired behaviour. Something must be done to correct this
 language behaviour.

Until D gets C++-like references, structs won't work for properties... You need to either make Vector3 a class, or x/y/z into Camera fields ? --anders
Dec 20 2004
parent reply nail <nail_member pathlink.com> writes:
In article <cq6ivh$3rt$1 digitaldaemon.com>,
=?ISO-8859-1?Q?Anders_F_Bj=F6rklund?= says...
nail wrote:

 Consider following situation. We have a struct named Vector3 it has members
 public x, y and z. Also we have a class Camera that has property, for example,
 eye. 
 
 In the code above "dead line" leads to changing y of the copy of the eye. I
 think this is not desired behaviour. Something must be done to correct this
 language behaviour.

Until D gets C++-like references, structs won't work for properties...

I don't want a reference for eye. I want double call of property setter / getter. First I get a property, then I change it, then I set property with additional operations coded in eye(Vector3 v)
You need to either make Vector3 a class, or x/y/z into Camera fields ?

First is impossible because of performance issues. Second is very annoying for example if I'd have not 3 members but 103.
--anders

Dec 20 2004
parent David Medlock <amedlock nospam.org> writes:
nail wrote:

You need to either make Vector3 a class, or x/y/z into Camera fields ?

First is impossible because of performance issues. Second is very annoying for example if I'd have not 3 members but 103.
--anders


Have you measured the performance issues? I am doing exactly this sort of coding and I see very small differences in struct/vs class ( at least compared to other parts of my code which use up more time). Typically I have 2 needs for Vectors/Points: 1. Equations such as what you posted, these are typically a minute portion of your running time. 2. Groups of them such as 3d Models where large quantities need to be manipulated. For #1, classes work fine. For #2 I simply made a VectorPool class which handles mass changes to the data and using vertex Arrays easy. Just my $0.02.
Dec 20 2004
prev sibling next sibling parent reply Ben Hinkle <bhinkle4 juno.com> writes:
nail wrote:

 Hi all.
 
 Consider following situation. We have a struct named Vector3 it has
 members public x, y and z. Also we have a class Camera that has property,
 for example, eye. The code is something like this:
 
 struct Vector3
 {
 float x, y, z;
 ...
 }
 
 class Camera
 {
 protected Vector3 _eye;
 
 public void eye(Vector3 v)
 {
 _eye = v;
 // do something more
 }
 
 public Vector3 eye()
 {
 return _eye;
 }
 }
 
 void main()
 {
 Camera cam = new Camera();
 cam.eye.y = 5; // dead line
 }
 
 In the code above "dead line" leads to changing y of the copy of the eye.
 I think this is not desired behaviour. Something must be done to correct
 this language behaviour. I don't know what is the best solution, but the
 following IMHO should work:
 If we write-access members/props of the struct returned by property then
 for writing second, write-property, must be called with parameter that is
 the changed copy of the read-property. ahem... I think I explain too
 complicately
 :). In another words, in the example above:
 1) cam.eye() returns a copy of original vector _eye
 2) 'y' component of this copy is changed to 5
 3) write-property came.eye(Vector3) must be called with parameter obtained
 in previous action.
 
 Comments?

There are some other options besides making language changes: 1) it seems like you want your class to "do something more" when the entire eye is set but not when just the y component is set. This seems odd to me so I assume it is an artifact of shortening the example for posting. So my first reaction is the current behavior is avoiding a bug in your code. 2) returning a pointer from eye() isn't too bad and makes it obvious it is a reference. The line cam.eye.y would then change the copy owned by the Camera. But as I said in 1 this seems like it seems like that opens up a hole to change _eye without doing something more. 3) add properties to just set or translate one component of eye at a time. For example void eyeY(float new_y) { _eye.y = new_y; //do something more } Then instead of cam.eye.y = 5 you have cam.eyeY = 5. 4) don't do anything and just leave the eye setter as the only way to change _eye. I don't think users will be confused by the behavior of cam.eye.y = 5 -Ben
Dec 20 2004
parent reply nail <nail_member pathlink.com> writes:
In article <cq6kee$59v$1 digitaldaemon.com>, Ben Hinkle says...
nail wrote:

 Hi all.
 
 Consider following situation. We have a struct named Vector3 it has
 members public x, y and z. Also we have a class Camera that has property,
 for example, eye. The code is something like this:
 
 struct Vector3
 {
 float x, y, z;
 ...
 }
 
 class Camera
 {
 protected Vector3 _eye;
 
 public void eye(Vector3 v)
 {
 _eye = v;
 // do something more
 }
 
 public Vector3 eye()
 {
 return _eye;
 }
 }
 
 void main()
 {
 Camera cam = new Camera();
 cam.eye.y = 5; // dead line
 }
 
 In the code above "dead line" leads to changing y of the copy of the eye.
 I think this is not desired behaviour. Something must be done to correct
 this language behaviour. I don't know what is the best solution, but the
 following IMHO should work:
 If we write-access members/props of the struct returned by property then
 for writing second, write-property, must be called with parameter that is
 the changed copy of the read-property. ahem... I think I explain too
 complicately
 :). In another words, in the example above:
 1) cam.eye() returns a copy of original vector _eye
 2) 'y' component of this copy is changed to 5
 3) write-property came.eye(Vector3) must be called with parameter obtained
 in previous action.
 
 Comments?

There are some other options besides making language changes: 1) it seems like you want your class to "do something more" when the entire eye is set but not when just the y component is set. This seems odd to me so I assume it is an artifact of shortening the example for posting. So my first reaction is the current behavior is avoiding a bug in your code.

"do something more" hides the following, for example: renderSystem.SetViewMatrix( Matrix44.lookAt(_eye, _target, _up) ); you can see that it is necessary to be called even if one component changed.
2) returning a pointer from eye() isn't too bad and makes it obvious it is a
reference. The line cam.eye.y would then change the copy owned by the
Camera. But as I said in 1 this seems like it seems like that opens up a
hole to change _eye without doing something more.

in this case additian action will not take place.
3) add properties to just set or translate one component of eye at a time.
For example
void eyeY(float new_y) { _eye.y = new_y; //do something more }
Then instead of cam.eye.y = 5 you have cam.eyeY = 5.

count of such separate methods will became innumerable at some moment
4) don't do anything and just leave the eye setter as the only way to change
_eye. I don't think users will be confused by the behavior of cam.eye.y = 5

I think expression cam.eye.y = 5 is very natural and so such constructions, writen because of blunder, can lead to hiden bugs hard to find.
Dec 20 2004
parent "Ben Hinkle" <bhinkle mathworks.com> writes:
[snip]

4) don't do anything and just leave the eye setter as the only way to


_eye. I don't think users will be confused by the behavior of cam.eye.y =


 I think expression cam.eye.y = 5 is very natural and so such

 writen because of blunder, can lead to hiden bugs hard to find.

So what about code like void foo(Vector3 w){w.y = 5;} ... foo(cam.eye) Does it modify the original values in cam? I would be surprised if it did - but then I'm not at all surprised that cam.eye.y changes a copy. Or if you really want to you can try something like struct EyeRef { Vector3* eye; Camera cam; ... void y(float new_y) { eye.y = new_y; cam.doSomething(); } Vector3 toValue(){ return *eye; } } class Camera { ... EyeRef eye() { EyeRef e; e.cam = this; e.eye = &_eye; return e; } } That way cam.eye.y = 5 will set the _eye.y value in cam and call doSomething in cam afterwards - which I think is what you want to have happen. Writing cam.eye.toValue will return a copy of the Vector3. Making the language more complex should be a last resort.
Dec 20 2004
prev sibling parent reply pragma <pragma_member pathlink.com> writes:
Nail, try changing your example to use the following:

public Vector3* eye()
{
    return &_eye;
}

Pointers aren't preferred in D (they're ugly), but they do exist for reasons aside from legacy compatibility. In this case, simply return a pointer to the struct you wish to access and all will be well. You won't have to change anything else, so don't worry about your code bloating up too badly. Also, if you're exposing a member in this fashion (exposing the whole struct), you might want to do away with an accessor completely. If there are no in/out clauses in your accessors, you're not gaining anything by that extra function call. :) I, like yourself, first expected structs to be pass-by-reference much like classes are; obviously, they are pass-by-value. The more D code I wrote, the more I became to realize that structs are more like heavyweight scalars than lightweight classes. They still have to obey the copy-on-write rule as other scalars do. I think this isn't a flaw in D's design, but rather a deliberate move on Walter's part to provide compatibility with C. To that end, it lets you do *at least* as much as C/C++, and then some. - Pragma at yahoo
Dec 20 2004
parent nail <nail_member pathlink.com> writes:
I, like yourself, first expected structs to be pass-by-reference much like
classes are; obviously, they are pass-by-value.  The more D code I wrote, the
more I became to realize that structs are more like heavyweight scalars than
lightweight classes.  They still have to obey the copy-on-write rule as other
scalars do.

I think this isn't a flaw in D's design, but rather a deliberate move on
Walter's part to provide compatibility with C.  To that end, it lets you do *at
least* as much as C/C++, and then some.

Maybe yo're right. The time will show.
Dec 20 2004