Jump to page: 1 2
Thread overview
Ref parameter: intended behavior or bug?
Aug 21, 2007
Mike Parker
Aug 21, 2007
BCS
Aug 21, 2007
Bill Baxter
Aug 22, 2007
Mike Parker
Aug 22, 2007
Regan Heath
Aug 22, 2007
Regan Heath
Aug 22, 2007
Mike Parker
Aug 23, 2007
Bill Baxter
Aug 22, 2007
Frits van Bommel
August 21, 2007
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.
August 21, 2007
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.


August 21, 2007
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
August 22, 2007
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.
August 22, 2007
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
August 22, 2007
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.
August 22, 2007
"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.


August 22, 2007
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
August 22, 2007
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.
August 23, 2007
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
« First   ‹ Prev
1 2