www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Ref parameter: intended behavior or bug?

reply Mike Parker <aldacron71 yahoo.com> writes:
I knocked up a sample program to demonstrate reference parameters, then 
got a result that wasn't at all what I expected:

====================================

import std.stdio;

struct MyStruct
{
	ubyte[] contents;
}

class MyClass
{
	ubyte[] contents;
}

void structDefault(MyStruct ms)
{
	writefln("Struct default: %d", ms.sizeof);
}

void structRef(ref MyStruct ms)
{
	writefln("Struct ref: %d", ms.sizeof);
}

void classDefault(MyClass mc)
{
	writefln("Class default: %d", mc.sizeof);
}

void main()
{
	MyStruct ms;
	MyClass mc = new MyClass;
	
	structDefault(ms);
	structRef(ms);
	classDefault(mc);
}
==================================

Here's the output:

Struct default: 8
Struct ref: 8
Class default: 4

The first and last are what I thought they would be, but I expected the 
Struct ref line to output 4, since it's supposed to be a ref parameter. 
I assume this to be a bug, but want to make sure there's not something 
intended going on.
Aug 21 2007
next sibling parent BCS <ao pathlink.com> writes:
Reply to Mike,

 I knocked up a sample program to demonstrate reference parameters,
 then got a result that wasn't at all what I expected:
 
 ====================================
 
 import std.stdio;
 
 struct MyStruct
 {
 ubyte[] contents;
 }
 class MyClass
 {
 ubyte[] contents;
 }
 void structDefault(MyStruct ms)
 {
 writefln("Struct default: %d", ms.sizeof);
 }
 void structRef(ref MyStruct ms)
 {
 writefln("Struct ref: %d", ms.sizeof);
 }
 void classDefault(MyClass mc)
 {
 writefln("Class default: %d", mc.sizeof);
 }
 void main()
 {
 MyStruct ms;
 MyClass mc = new MyClass;
 structDefault(ms);
 structRef(ms);
 classDefault(mc);
 }
 ==================================
 
 Here's the output:
 
 Struct default: 8
 Struct ref: 8
 Class default: 4
 The first and last are what I thought they would be, but I expected
 the Struct ref line to output 4, since it's supposed to be a ref
 parameter. I assume this to be a bug, but want to make sure there's
 not something intended going on.
 

I think the compiler is playing games with the perceived type of ref arguments. I would guess that the type of ms in strutRef would end up being the same as the type in structDefault.
Aug 21 2007
prev sibling parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Mike Parker wrote:
 I knocked up a sample program to demonstrate reference parameters, then 
 got a result that wasn't at all what I expected:
 
 ====================================
 
 import std.stdio;
 
 struct MyStruct
 {
     ubyte[] contents;
 }
 
 class MyClass
 {
     ubyte[] contents;
 }
 
 void structDefault(MyStruct ms)
 {
     writefln("Struct default: %d", ms.sizeof);
 }
 
 void structRef(ref MyStruct ms)
 {
     writefln("Struct ref: %d", ms.sizeof);
 }
 
 void classDefault(MyClass mc)
 {
     writefln("Class default: %d", mc.sizeof);
 }
 
 void main()
 {
     MyStruct ms;
     MyClass mc = new MyClass;
     
     structDefault(ms);
     structRef(ms);
     classDefault(mc);
 }
 ==================================
 
 Here's the output:
 
 Struct default: 8
 Struct ref: 8
 Class default: 4
 
 The first and last are what I thought they would be, but I expected the 
 Struct ref line to output 4, since it's supposed to be a ref parameter. 
 I assume this to be a bug, but want to make sure there's not something 
 intended going on.

'ref' is not a type constructor in D, it is a storage class. .sizeof gives the size of the type. The storage class shouldn't affect that. --bb
Aug 21 2007
parent reply Mike Parker <aldacron71 yahoo.com> writes:
Bill Baxter wrote:
 Mike Parker wrote:
 I knocked up a sample program to demonstrate reference parameters, 
 then got a result that wasn't at all what I expected:

 ====================================

 import std.stdio;

 struct MyStruct
 {
     ubyte[] contents;
 }

 class MyClass
 {
     ubyte[] contents;
 }

 void structDefault(MyStruct ms)
 {
     writefln("Struct default: %d", ms.sizeof);
 }

 void structRef(ref MyStruct ms)
 {
     writefln("Struct ref: %d", ms.sizeof);
 }

 void classDefault(MyClass mc)
 {
     writefln("Class default: %d", mc.sizeof);
 }

 void main()
 {
     MyStruct ms;
     MyClass mc = new MyClass;
         structDefault(ms);
     structRef(ms);
     classDefault(mc);
 }
 ==================================

 Here's the output:

 Struct default: 8
 Struct ref: 8
 Class default: 4

 The first and last are what I thought they would be, but I expected 
 the Struct ref line to output 4, since it's supposed to be a ref 
 parameter. I assume this to be a bug, but want to make sure there's 
 not something intended going on.

'ref' is not a type constructor in D, it is a storage class. .sizeof gives the size of the type. The storage class shouldn't affect that.

But it's a bit inconsistent, is it not? Consider this function: void structPtr(MyStruct* ms) { writefln("Struct ptr: %d", ms.sizeof); } This will print 4. Why should the behavior of 'ref' be any different than that of '*'? If I want the size of the type, I would use MyStruct.sizeof.
Aug 22 2007
next sibling parent reply Regan Heath <regan netmail.co.nz> writes:
Mike Parker wrote:
 Bill Baxter wrote:
 Mike Parker wrote:
 I knocked up a sample program to demonstrate reference parameters, 
 then got a result that wasn't at all what I expected:

 ====================================

 import std.stdio;

 struct MyStruct
 {
     ubyte[] contents;
 }

 class MyClass
 {
     ubyte[] contents;
 }

 void structDefault(MyStruct ms)
 {
     writefln("Struct default: %d", ms.sizeof);
 }

 void structRef(ref MyStruct ms)
 {
     writefln("Struct ref: %d", ms.sizeof);
 }

 void classDefault(MyClass mc)
 {
     writefln("Class default: %d", mc.sizeof);
 }

 void main()
 {
     MyStruct ms;
     MyClass mc = new MyClass;
         structDefault(ms);
     structRef(ms);
     classDefault(mc);
 }
 ==================================

 Here's the output:

 Struct default: 8
 Struct ref: 8
 Class default: 4

 The first and last are what I thought they would be, but I expected 
 the Struct ref line to output 4, since it's supposed to be a ref 
 parameter. I assume this to be a bug, but want to make sure there's 
 not something intended going on.

'ref' is not a type constructor in D, it is a storage class. .sizeof gives the size of the type. The storage class shouldn't affect that.

But it's a bit inconsistent, is it not? Consider this function: void structPtr(MyStruct* ms) { writefln("Struct ptr: %d", ms.sizeof); } This will print 4.

Of course, you're asking for the size of a pointer here and a pointer on a 32 bit system is _always_ 32 bits or 4 bytes.
 Why should the behavior of 'ref' be any different 
 than that of '*'? 

Because 'ref' is not '*'. To turn it around; Why should the behaviour of 'ref' be the same as '*'? Is the only point of having 'ref' to allow you to pass variables without & at the call site? I believe the main reason for 'ref' was/is to allow you to modify the variable being passed. So what advantage does 'ref' have over '*'? If you ask me, in D, 'ref' has perhaps 1 small advantage, and 1 large liability. The advantage: void foo(ref int a) { a = 5; } //cleaner syntax void foo(int* a) { *a = 5; } Consider a struct however: struct Foo { int a; } void foo(ref Foo f) { f.a = 5; } void foo(Foo* f) { f.a = 5; } //identical syntax Granted you still have: struct Foo { int a; } void foo(ref Foo f) { f = <some other Foo>; } //cleaner syntax void foo(Foo* f) { *f = <some other Foo>; } However, the liability (IMO): Which of these functions modifies 'foo'? Foo foo; bar(foo); baz(foo); You can't tell without inspecting the function signatures. Whereas with pointers: Foo foo; bar(&foo); baz(foo); It's quite clear, unless of course baz is passing by reference ;)
 If I want the size of the type, I would use
 MyStruct.sizeof.

I think the concept of passing by reference is that when you pass by ref the parameter _is_ the original variable in every possible way. The compiler might be using a proxy object to implement this, it might not be, IANACW. As such when you ask for the size of a ref parameter you're asking for the size of the original variable, which in this case is the size of the struct itself. Regan
Aug 22 2007
next sibling parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Regan Heath" <regan netmail.co.nz> wrote in message 
news:fah2ic$2e20$1 digitalmars.com...
 However, the liability (IMO):

   Which of these functions modifies 'foo'?

   Foo foo;
   bar(foo);
   baz(foo);

 You can't tell without inspecting the function signatures. Whereas with 
 pointers:

   Foo foo;
   bar(&foo);
   baz(foo);

 It's quite clear, unless of course baz is passing by reference ;)

If D required 'ref' and 'out' at call site like C#, this wouldn't be an issue.
Aug 22 2007
parent Regan Heath <regan netmail.co.nz> writes:
Jarrett Billingsley wrote:
 "Regan Heath" <regan netmail.co.nz> wrote in message 
 news:fah2ic$2e20$1 digitalmars.com...
 However, the liability (IMO):

   Which of these functions modifies 'foo'?

   Foo foo;
   bar(foo);
   baz(foo);

 You can't tell without inspecting the function signatures. Whereas with 
 pointers:

   Foo foo;
   bar(&foo);
   baz(foo);

 It's quite clear, unless of course baz is passing by reference ;)

If D required 'ref' and 'out' at call site like C#, this wouldn't be an issue.

True. I think I'd prefer just to use pointers. Then, you're writing & at call site and *param when you're modifying the parameter itself, both of which are explicit and easy to see. D automatically dereferences member operations for pointers to structs so the syntax is already fine there. The one problem you have are operator overloads, eg. struct Foo { ... } Foo foo(Foo* a, Foo* b) { return a * b; } has to be written: Foo foo(Foo* a, Foo* b) { return *a * *b; } which is a bit ick. Regan
Aug 22 2007
prev sibling parent reply Mike Parker <aldacron71 yahoo.com> writes:
Regan Heath wrote:

 
 I think the concept of passing by reference is that when you pass by ref 
 the parameter _is_ the original variable in every possible way.  The 
 compiler might be using a proxy object to implement this, it might not 
 be, IANACW.  As such when you ask for the size of a ref parameter you're 
 asking for the size of the original variable, which in this case is the 
 size of the struct itself.

Yeah, I get it, but it still /feels/ inconsistent to me. If the size of a class reference is 4 bytes, then it seems to me that the size of a ref parameter should be the same and not the size of the object to which it refers. But I suppose we aren't supposed to think of a ref parameter as a true reference, but rather as a mutable view of the original data. In which case, 'inout' seems more appropriate, in retrospect, than 'ref'. OK, misconception cleared, situation grokked.
Aug 22 2007
next sibling parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Mike Parker wrote:
 Regan Heath wrote:
 
 I think the concept of passing by reference is that when you pass by 
 ref the parameter _is_ the original variable in every possible way.  
 The compiler might be using a proxy object to implement this, it might 
 not be, IANACW.  As such when you ask for the size of a ref parameter 
 you're asking for the size of the original variable, which in this 
 case is the size of the struct itself.

Yeah, I get it, but it still /feels/ inconsistent to me. If the size of a class reference is 4 bytes, then it seems to me that the size of a ref parameter should be the same and not the size of the object to which it refers.

Consider the sizeof to mean how much space you would have to reserve if you were going to copy the thing into opaque memory somewhere. If you try to copy a class reference then you need exactly 4 bytes. (You cannot make a copy of the thing referred to.) But if you tried to make a copy of a ref-passed struct{float x,y,z} then you would indeed need 12 bytes because trying to copy the ref parameter will actually copy the thing referred to. You can't make a copy of the reference itself. At least not without dipping into pointer shenanigans.
 But I suppose we aren't supposed to think of a ref parameter as 
 a true reference, but rather as a mutable view of the original data. In 
 which case, 'inout' seems more appropriate, in retrospect, than 'ref'.

Except someday soon we'll probably have working 'const ref' parameters, and 'const inout' makes no sense. --bb
Aug 22 2007
prev sibling parent "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Mike Parker" <aldacron71 yahoo.com> wrote in message 
news:faiicj$2hms$1 digitalmars.com...
 Yeah, I get it, but it still /feels/ inconsistent to me. If the size of a 
 class reference is 4 bytes, then it seems to me that the size of a ref 
 parameter should be the same and not the size of the object to which it 
 refers. But I suppose we aren't supposed to think of a ref parameter as a 
 true reference, but rather as a mutable view of the original data.

Right, which is more of how I've understood Walter's intentions for 'ref': the semantics imply a mutable view of a piece of data, but the actual implementation should not be revealed. The compiler could, for example, pass the parameter by value into the function, and then when the function returns, copy that parameter back into where it came from, in which case no actual pointers are being used. Of course this is a roundabout and ineffecient way of implementing 'ref', but according to the semantics, a valid one.
Aug 22 2007
prev sibling parent Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Mike Parker wrote:
 Bill Baxter wrote:
 'ref' is not a type constructor in D, it is a storage class.
 .sizeof gives the size of the type.  The storage class shouldn't 
 affect that.

But it's a bit inconsistent, is it not? Consider this function: void structPtr(MyStruct* ms) { writefln("Struct ptr: %d", ms.sizeof); } This will print 4. Why should the behavior of 'ref' be any different than that of '*'? If I want the size of the type, I would use MyStruct.sizeof.

Because 'ref' also adds extra '*'s in front of every place the variable occurs, so your code with 'ref' is more like ----- void structPtr(ref MyStruct ms) { writefln("Struct ptr: %d", (*ms).sizeof); } ----- so you print the size of the struct referred to, not the size of the reference itself.
Aug 22 2007