January 20, 2007
Walter Bright wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>> No, and for a good reason:
>>
>> foo(LargeStruct s1, LargeStruct s2)
>> {
>>   ...
>> }
>> ...
>> LargeStruct s;
>> foo(s, s);
>>
>> Nobody would enjoy hidden aliasing of s1 and s2.
> 
> There's another aliasing case:
> 
> LargeStruct s;
> foo(s);
> ... here some other thread modifies s ...
> 
> And foo() suddenly has its argument values change unexpectedly.
> 
> Value and reference type usages mostly overlap, but they are fundamentally different, and having the compiler switch between the two in some implementation-defined manner (as suggested by others more than once) is not going to work.
> 
> If one is needing reference behavior from a struct, seriously consider making it a class instead. Or even just take the address of it and pass the pointer.

If we're going to pass a pointer to it, then we might as well just make it inout and avoid having to explicitly use '&' to take the address.

The question is how can we write functions in D that take big structs as parameters in such a way that it is:
A) efficient and
B) guaranteed not to change the caller's value

In C++ the answer is const LargeStruct&.  In D the answer is...

--bb
January 20, 2007
Bill Baxter wrote:
> The question is how can we write functions in D that take big structs as parameters in such a way that it is:
> A) efficient and
> B) guaranteed not to change the caller's value
> 
> In C++ the answer is const LargeStruct&.

C++ doesn't *guarantee* it won't change. There are at least two legal ways to do it.

> In D the answer is...

There aren't const references in D. But the 3 ways to pass it by pointer are:

1) inout
2) take the address and use a pointer
3) make it a class instead
January 20, 2007
Bill Baxter wrote:
> Walter Bright wrote:
>> Andrei Alexandrescu (See Website For Email) wrote:
>>> No, and for a good reason:
>>>
>>> foo(LargeStruct s1, LargeStruct s2)
>>> {
>>>   ...
>>> }
>>> ...
>>> LargeStruct s;
>>> foo(s, s);
>>>
>>> Nobody would enjoy hidden aliasing of s1 and s2.
>>
>> There's another aliasing case:
>>
>> LargeStruct s;
>> foo(s);
>> ... here some other thread modifies s ...

This exists in C++ with const reference parameters.  But I've not heard much gnashing of teeth over this.  Maybe people are getting worried about it more these days, but since I started learning C++ I've seen lots of code that uses const references all over the place.

But I can see that it is important to make the distinction in the header for those who are writing multithreaded code.

Which leaves us with two alternatives:
1) add some sort of const-reference construct to D
2) leave it as is.


I have to say that 2) is probably going to send me packing.  I've always assumed that D would get some sort of const reference someday but it sounds like you're saying you don't see a need.


But I think this raytracer example shows very clearly that using even moderately sized structs (i.e. 3-6 floats, not even that big) as parameters in critical loops can degrade performance significantly.

Switching to a class is not really an option.  That's going to be even slower given that intermediate vector calculations are needed all over the place.

So the other option is scope classes which someone suggested on the other thread.  Is this really an option?  I haven't played with it that much, so I don't really know what the issues are or what the syntax looks like in practice.  But I believe at least that it means every place you use LargeStruct you're going to have to say  'scope LargeStruct', right?  That's fairly ugly to have to say everywhere for something that's going to get heavy use.  Especially when in the end it really *is* a value type.  So it should be a struct.  Period.

--bb
January 20, 2007
Walter Bright wrote:
> Bill Baxter wrote:
>> The question is how can we write functions in D that take big structs as parameters in such a way that it is:
>> A) efficient and
>> B) guaranteed not to change the caller's value
>>
>> In C++ the answer is const LargeStruct&.
> 
> C++ doesn't *guarantee* it won't change. There are at least two legal ways to do it.

Very true.  What I'm after is really a concise way to both
a) document to readers of the code that it won't change and
b) get the compiler to zap me if I accidentally try to modify it.

>> In D the answer is...
> 
> There aren't const references in D. But the 3 ways to pass it by pointer are:
> 
> 1) inout
> 2) take the address and use a pointer
> 3) make it a class instead

And none of those accomplishes a or b.

--bb
January 20, 2007
Walter Bright wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>> No, and for a good reason:
>>
>> foo(LargeStruct s1, LargeStruct s2)
>> {
>>   ...
>> }
>> ...
>> LargeStruct s;
>> foo(s, s);
>>
>> Nobody would enjoy hidden aliasing of s1 and s2.

I think you mean more like:


foo(LargeStruct s1, LargeStruct inout s2)
{
   ...
}
...
LargeStruct s;
foo(s, s);

In cases where there is an inout, I think the compiler would have to fallback on the old one.  The one below that Walter pointed out is more troublesome.

> 
> There's another aliasing case:
> 
> LargeStruct s;
> foo(s);
> ... here some other thread modifies s ...
> 
> And foo() suddenly has its argument values change unexpectedly.
> 
> Value and reference type usages mostly overlap, but they are fundamentally different, and having the compiler switch between the two in some implementation-defined manner (as suggested by others more than once) is not going to work.
> 
> If one is needing reference behavior from a struct, seriously consider making it a class instead. Or even just take the address of it and pass the pointer.

I never thought about that one.  Of course your right about this like normal.  Humm... what about copy on read/write...

struct A
{
  byte[100] b;
}

//My contrived example

void foo(in A x, in A y)
{
   if (g)
   {
     if (x)
     {
        //...
     }
     else
     {
        if (y)
        {
           //...

	}
     }
   }
}

//Would convert too.

void foo(inout A x, inout A y) //Both treated as in/out
{
   if (g)
   {
     A x2 = x;  //First time x is used
     if (x2)
     {
        //...
     }
     else
     {
        A y2 = y;  //First time y is used
        if (y2)
        {
           //...

	}
     }
   }
}

if (g) was false you would avoid the expensive copy of x2 to the stack.   Of course the tricky part is working out when this optimisation is worth it.  The function could actually run slower because there are 2 more values on the stack.

Ok, I spotted a floor in that design also.  A thread event could occur  between the copy of x2 and y2 (or at g).  I guess this would mean the user would have to somehow indicate that that is ok.

Ok here's yet another idea.

void bar(in A a)
{
  //....
}

void foo(in A a)
{
   bar(a);
   bar(a);
}

In this example we already have an in copy of A, so why copy it again? We do not have a reference and foo isn't doing any threading operations.  If this was inlined it would be optimized away, however if it can't be inlined it should still be possible to optimize it away.

//Here's another example of the some thing

void foo()
{
   A a;
   bar(a);
   //End of function A goes out of scope and A is destroyed.
}

-Joel
January 20, 2007
Bill Baxter wrote:
> Walter Bright wrote:
>> Bill Baxter wrote:
>>> The question is how can we write functions in D that take big structs as parameters in such a way that it is:
>>> A) efficient and
>>> B) guaranteed not to change the caller's value
>>>
>>> In C++ the answer is const LargeStruct&.
>>
>> C++ doesn't *guarantee* it won't change. There are at least two legal ways to do it.
> 
> Very true.  What I'm after is really a concise way to both
> a) document to readers of the code that it won't change and
> b) get the compiler to zap me if I accidentally try to modify it.
> 
>>> In D the answer is...
>>
>> There aren't const references in D. But the 3 ways to pass it by pointer are:
>>
>> 1) inout
>> 2) take the address and use a pointer
>> 3) make it a class instead
> 
> And none of those accomplishes a or b.
> 
> --bb

Ah the great const debate. I have a feeling that this discussion will be a major part of D 2.0.
January 20, 2007
Bill Baxter wrote:
> Walter Bright wrote:
>> Bill Baxter wrote:
>>> The question is how can we write functions in D that take big structs as parameters in such a way that it is:
>>> A) efficient and
>>> B) guaranteed not to change the caller's value
>>>
>>> In C++ the answer is const LargeStruct&.
>>
>> C++ doesn't *guarantee* it won't change. There are at least two legal ways to do it.
> 
> Very true.  What I'm after is really a concise way to both
> a) document to readers of the code that it won't change and
> b) get the compiler to zap me if I accidentally try to modify it.
> 
>>> In D the answer is...
>>
>> There aren't const references in D. But the 3 ways to pass it by pointer are:
>>
>> 1) inout
>> 2) take the address and use a pointer
>> 3) make it a class instead
> 
> And none of those accomplishes a or b.
> 
> --bb

Just a thought: B) can be accomplished with an Interface that only exposes gettors.
Granted, though, that 1) it means defining everything you want to be read-only as a
property, but these would be optimized away by inlining anyhow, 2) you have to write an
Interface (which isn't a big deal, IMHO, but I know some people can be annoyed with them).
 Also, you would want to make the implementor class and all methods 'final' to avoid
potential virtuality overhead.

Something akin to:
<code>
interface IFoo {
  int alpha () ;
  int beta  () ;
}

final class Foo : IFoo {
  private {
    int m_alpha ;
    int m_beta  ;
  }

  final int alpha () { return m_alpha ; }
  final int beta  () { return m_beta  ; }

  final int alpha (int x) { return m_alpha = x ; }
  final int beta  (int x) { return m_beta  = x ; }
}

void somefunc (IFoo obj) {
  // do stuff with obj.alpha and obj.beta
}

auto myfoo = new Foo;
somefunc(myfoo);
</code>

-- Chris Nicholson-Sauls
January 20, 2007
>>  There's another aliasing case:
>>  LargeStruct s;
>> foo(s);
>> ... here some other thread modifies s ...

Okay, I can see that this could result in unexpected behaviour. The idea of determining whether to copy or not during compile time was not a good one at all. What sparked this suggestion was exactly the situation Bill Baxter described:

> The question is how can we write functions in D that take big structs as parameters in such a way that it is:
> A) efficient and
> B) guaranteed not to change the caller's value

If inout is used, it doesn't guarantee B) and even worse: I'm lying to anyone reading the function signature. A pointer will also require additional documentation to keep users from wondering whether their data is changed or not. Making the struct a class is not an option because of A).

Maybe it's just time to accept that D just doesn't do const the C++ way and B) can't be expressed.

Christian
January 20, 2007
"Kyle Furlong" <kylefurlong@gmail.com> wrote:
> Ah the great const debate. I have a feeling that this discussion will be a major part of D 2.0.

I wish we'd gone the const by default way before releasing 1.0, no matter how much code would've been broken... It's so much harder now to get something as good into the language, as it's more likely that production code is being written with D, and we have to think of backwards compatibility. That wasn't much of an issue before.


January 20, 2007
Christian Kamm wrote:
> 
>>>  There's another aliasing case:
>>>  LargeStruct s;
>>> foo(s);
>>> ... here some other thread modifies s ...
> 
> Okay, I can see that this could result in unexpected behaviour. The idea of determining whether to copy or not during compile time was not a good one at all. What sparked this suggestion was exactly the situation Bill Baxter described:
> 
>> The question is how can we write functions in D that take big structs as parameters in such a way that it is:
>> A) efficient and
>> B) guaranteed not to change the caller's value
> 
> If inout is used, it doesn't guarantee B) and even worse: I'm lying to anyone reading the function signature. A pointer will also require 

I agree, I don't like the thought of us having to use inout as an optimization. That's not what it's meant for. Leaving aside the notion of 'const' right now, I think 'byref' or some such would work fine for this purpose. It tells a function user "this is passed by reference" and is easily grep-able (unlike '&').

And a pointer will require the use of '&' at every call site instead of 'byref' just once in the function definition.

> additional documentation to keep users from wondering whether their data is changed or not. Making the struct a class is not an option because of A).
> 
> Maybe it's just time to accept that D just doesn't do const the C++ way and B) can't be expressed.
> 
> Christian