View mode: basic / threaded / horizontal-split · Log in · Help
November 07, 2012
Re: Const ref and rvalues again...
On Wednesday, 7 November 2012 at 03:13:22 UTC, martin wrote:
>> void func1(ref int x);  //D lvalue-only ref
>> void func2(@ref int x); //works like c++'s ref
>>
>> Seems fairly easy to tell apart, and still leaves const-ness 
>> as an option.
>
> Afaik C++ doesn't allow rvalues to be passed to _mutable_ 
> references (T&), only to const references, making perfect sense 
> imo. I really do not see the point for an additional syntax for 
> C++-like const references (const T&) which would also take 
> rvalues.

> Please give me an example where you want to pass an rvalue to a 
> _mutable_ reference parameter. I would simply continue to 
> disallow that, since that would mean that changes to the 
> referenced rvalue would not be visible for the caller (a 
> temporary/literal is changed - how could someone possibly want 
> that?).

 Still that zlib entry is coming to mind. But more importantly is 
that you still need code duplication to get both accessible.

 //many possible combinations thereof
 int func(const ref x);

 int func(int x) {
   return func(cast(const int) x);
 }


 But regarding zlib.. It's function is something like:
 int compress(char *output, int *size, char *input, int 
inputSize);

 So... if we convert that to something similar we get..

 enum ZlibEnum {}
 ZlibEnum compress(void[] output, void[] input, &ref Zlib state) 
nothrow pure;

 The idea in this case is 'state' would continue to hold the 
input/output pointers and all information needed, so you could 
continue to use it and if it has any 'unflushed' data (output 
size too small?) then it could retain that. however if you knew 
you didn't need it you could ignore it.

 ZlibEnum could be for output C calling code, so success/failure 
needs to be known right away. It makes sense for it to return a 
Zlib type as well, but depends on what has higher priority and 
why.

 string input = "something long";
 ubyte[1000] output;
 ubyte[5] output2;
 Zlib state;

 compress(output, input, null); //we know the buffer is large 
enough
 compress(output, input, state);
November 07, 2012
Re: Const ref and rvalues again...
On Wednesday, 7 November 2012 at 03:13:22 UTC, martin wrote:
>> void func1(ref int x);  //D lvalue-only ref
>> void func2(@ref int x); //works like c++'s ref
>>
>> Seems fairly easy to tell apart, and still leaves const-ness 
>> as an option.
>
> Afaik C++ doesn't allow rvalues to be passed to _mutable_ 
> references (T&), only to const references, making perfect sense 
> imo. I really do not see the point for an additional syntax for 
> C++-like const references (const T&) which would also take 
> rvalues.

> Please give me an example where you want to pass an rvalue to a 
> _mutable_ reference parameter. I would simply continue to 
> disallow that, since that would mean that changes to the 
> referenced rvalue would not be visible for the caller (a 
> temporary/literal is changed - how could someone possibly want 
> that?).

  Still that zlib entry is coming to mind. But more importantly is
that you still need code duplication to get both accessible.

  //many possible combinations thereof
  int func(const ref x);

  int func(int x) {
    return func(cast(const int) x);
  }


  But regarding zlib.. It's function is something like:
  int compress(char *output, int *size, char *input, int
inputSize);

  So... if we convert that to something similar we get..

  enum ZlibEnum {}
  ZlibEnum compress(void[] output, void[] input, &ref Zlib state)
nothrow pure;

  The idea in this case is 'state' would continue to hold the
input/output pointers and all information needed, so you could
continue to use it and if it has any 'unflushed' data (output
size too small?) then it could retain that. however if you knew
you didn't need it you could ignore it.

  ZlibEnum could be for output C calling code, so success/failure
needs to be known right away. It makes sense for it to return a
Zlib type as well, but depends on what has higher priority and
why.

  string input = "something long";
  ubyte[1000] output;
  ubyte[5] output2;
  Zlib state;

  compress(output, input, null); //we know the buffer is large
enough
  compress(output2, input, state);
November 07, 2012
Re: Const ref and rvalues again...
On Wednesday, 7 November 2012 at 03:35:19 UTC, Era Scarecrow 
wrote:
> Still that zlib entry is coming to mind. But more importantly
> is that you still need code duplication to get both accessible.
>
> //many possible combinations thereof
> int func(const ref x);
>
> int func(int x) {
>   return func(cast(const int) x);
> }

int func(in ref int x);
int func(int x) { return func(x); }

The latter overload is for rvalues (no need to cast to const) and 
wouldn't be required if the first one took rvalues directly 
(having a _const_ ref parameter). That's not an example for 
mutable references though, it's basically a shortcut so as to not 
having to declare all rvalues for x manually.

> enum ZlibEnum {}
> ZlibEnum compress(void[] output, void[] input,
>   &ref Zlib state) nothrow pure;
>
> string input = "something long";
> ubyte[1000] output;
> ubyte[5] output2;
> Zlib state;
>
> compress(output, input, null);
> compress(output2, input, state);

Where's the rvalue? You're talking about an optional mutable 
reference here, which is only doable via a pointer (references 
cannot be null, not in C++ and not in D):

ZlibEnum compress(void[] output, void[] input, Zlib* state = 
null);
...
compress(output, input);
compress(output2, input, &state);

That would be correct. But you're completely missing the point 
here. Let's assume you wanted to require a state to be passed:

ZlibEnum compress(void[] output, void[] input, ref Zlib state);

You could now use:

Zlib state; // lvalue
compress(output, input, state);

but not:

compress(output, input, Zlib()); // rvalue

And that is most likely a good thing, since your state would be 
lost after the compress() call. If you don't want it, you don't 
pass it, and as I said, the only way to do that is to pass a 
nullable pointer. So this is off-topic I'm afraid.
November 07, 2012
Re: Const ref and rvalues again...
On Wednesday, 7 November 2012 at 04:05:32 UTC, martin wrote:
> int func(in ref int x);
> int func(int x) { return func(x); }
>
> The latter overload is for rvalues (no need to cast to const) 
> and wouldn't be required if the first one took rvalues directly 
> (having a _const_ ref parameter). That's not an example for 
> mutable references though, it's basically a shortcut so as to 
> not having to declare all rvalues for x manually.

 Maybe... But when working with non built-in types can you 
guarantee that behavior?

//same as 'const ref' basically, unless 'in ref' is accepted
struct S
//int func(in ref S x); //?
int func(const ref S x);
int func(S x) { return func(x); } //potentially infinitely self 
calling

>> compress(output, input, null);
>> compress(output2, input, state);
>
> Where's the rvalue? You're talking about an optional mutable 
> reference here, which is only doable via a pointer (references 
> cannot be null, not in C++ and not in D):

 Perhaps null is wrong in this case, but had it been Zlib.init or 
Zlib(), then it would still be applicable. Since it's a reference 
I would think it could accept a null pointer and realize to 
create a temporary which has it's default values.

> That would be correct. But you're completely missing the point 
> here. Let's assume you wanted to require a state to be passed:
>
> ZlibEnum compress(void[] output, void[] input, ref Zlib state);
>
> You could now use:
>
> Zlib state; // lvalue
> compress(output, input, state);
>
> but not:
>
> compress(output, input, Zlib()); // rvalue
>
> And that is most likely a good thing, since your state would be 
> lost after the compress() call. If you don't want it, you don't 
> pass it, and as I said, the only way to do that is to pass a 
> nullable pointer. So this is off-topic I'm afraid.

 True, but rather than having to create a temporary you don't 
plan on using just to satisfy the signature, and you know you 
don't NEED it afterwards, why do you have to go through the extra 
steps? How many wrappers are made in general just to work around 
minor issues like this?

 Quite often we ignore return types if they aren't interesting, 
but we can't ignore a rvalue we only need for one call (or just 
to satisfy the signature)? Say we have a divide function (who 
knows what for), and this is more efficient than normal. So...

 //perhaps '@ref int remainder = 0'?
 int divide(int number, int divisor, ref int remainder);

 Now in this case what if we don't care about the remainder? 
Create a temporary (or a wrapper function)?

 //satisfy remainder
 int divide(int number, int divisor) {
   int tmp; return divide(number, divisor, tmp);
 }

 int modulus(int number, int divisor) {
   int tmp; divide(number, divisor, tmp);
   return tmp;
 }

 But if we want the result of the division AND the remainder 
should we have to make a separate modulus function when the 
original divide can clearly give you the answer? This cuts closer 
to the instruction set as an example.
November 07, 2012
Re: Const ref and rvalues again...
On Wednesday, 7 November 2012 at 04:36:59 UTC, Era Scarecrow 
wrote:
> Maybe... But when working with non built-in types can you 
> guarantee that behavior?

Sure, try it out.

What you are referring to here has nothing to do with rvalues. It 
is about side results which are only sometimes needed. It is 
clear that you have to use lvalue arguments for those in order to 
access them after the function call. Using bogus rvalue arguments 
in case you're not interested in them wouldn't really help - 
you'd save the variable declaration, but the function signature 
would still be ugly, and you can't always provide hypothetical 
default values to hide them (depending on parameter order). In my 
experience, these cases are very rare anyway (unless dealing with 
porting old C-style code which you seem to be doing) and function 
overloading is the most elegant way to handle it - take a look at 
the large number of overloads in modern language libraries such 
as .NET.

This is how I would implement it:

// magical, super-fast function ;)
int divide(in int number, in int divisor, int* remainder = null);
auto bla = divide(13, 4); // => 3

// remainder needed as well, hiding the pointer as reference:
int divide(in int number, in int divisor, out int remainder)
{
    return divide(number, divisor, &remainder);
}
int remainder;
auto bla = divide(13, 4, remainder); // => 3, remainder = 1

// only remainder needed:
int modulus(in int number, in int divisor)
{
    int tmp;
    divide(number, divisor, tmp);
    return tmp;
}
auto bla = modulus(13, 4); // => 1

But this is all really off-topic here, let's stop spamming this 
thread.
November 07, 2012
Re: Const ref and rvalues again...
On Wednesday, 7 November 2012 at 02:34:25 UTC, martin wrote:
> On Wednesday, 7 November 2012 at 02:06:09 UTC, Rob T wrote:
>> What about the case where we want to pass a source argument 
>> either by reference or as a copy depending on the l/r value 
>> situation?
>>
>> eg
>> void f( ref a );
>> void f( a );
>>
>> --rt
>
> I don't get what you mean - that's why the 2 overloads are for 
> (you forgot the const/in keyword - beware! :))
>
> void f( in ref T a );
> void f( in T a );
>
> rvalue binds to the latter overload (argument not copied, but 
> moved directly).
> lvalue binds to the first overload (reference).
> Works with v2.060.

Sorry, my explanation was very poor. I'll try again.

I'm trying to describe the case where you want to modify the ref 
argument so that the source is also changed. For example, let's 
say you want to move a resource instead of coping it. Moving 
means the resource is transfered from one object to another, and 
the original is reset without destoying the resource. That way 
only one object can have the same resource. So for moving a 
resource, you cannot use "in ref" or "const ref", you need to 
just use "ref".

void f( ref T a ){
  // take resource away from a
  this.resource = a.resource;
  a.resetResource();
  // this now owns the resource
}

ref T works fine, but if you wish to use f( ref T a ) on a temp 
value returned from another function call, you'll need to 
overload f() to pass by value, which means creating a duplicate.

void f( ref T a ){
  // take resource away from a
  this.resource = a.resource;
  a.resetResource();
  // this now owns the resource
}

Example:

T g(){
   T temp;
   // create resource which is stored inside T
   temp.createResource();
   // move temp contents to return value
   return move(temp);
}

f( g() ); // won't compile with ref

It's annoying to have to create overloaded duplicates for a 
situation like moving, esp if the duplicate is a lot of code. In 
C++ the move problem was solved using && move semantics, and this 
is what I'm trying to emulate in D but I feel like I'm fighting 
with the language.

It is possible that D has a specific way of solving this problem 
that I'm not aware of yet, so correct me if I'm asking for 
something that is simply not needed.

I tried f( move(g()) ) but that fails to work. My best guess is 
that D does a hidden move of the temp instead of a copy to value. 
I can't say for sure because the documentation is not clear and 
is missing important details like this. I also cannot rely on 
clever compiler optimizations that may or may not be implemented 
as a guarantee.

--rt
November 07, 2012
Re: Const ref and rvalues again...
On 11/07/2012 04:13 AM, martin wrote:
> On Wednesday, 7 November 2012 at 02:58:57 UTC, Era Scarecrow wrote:
>> Maybe a very simple change/addition; Like perhaps @ref? (Attribute
>> ref, Alternate ref, auto ref.. All sorta fit for it's meaning).
>>
>> So...
>>
>> void func1(ref int x);  //D lvalue-only ref
>> void func2(@ref int x); //works like c++'s ref
>>
>> Seems fairly easy to tell apart, and still leaves const-ness as an
>> option.
>
> Afaik C++ doesn't allow rvalues to be passed to _mutable_ references
> (T&), only to const references, making perfect sense imo. I really do
> not see the point for an additional syntax for C++-like const references
> (const T&) which would also take rvalues.

You are still missing that const in C++ is different from const in D.
Also, if the point is to have higher speed, why shouldn't the function 
be allowed to use an rvalue as scratch space without a _deep copy_ ?

const in C++ does not mean anything. It is just loosely enforced 
interface documentation. const in D actually restricts what the callee 
can do with the argument, in a transitive fashion.

> Please give me an example where you want to pass an rvalue to a
> _mutable_ reference parameter.


When my struct does not support any operations that are const, and 
creating a mutable copy is not possible due to indirections?

> I would simply continue to disallow that,
> since that would mean that changes to the referenced rvalue would not be
> visible for the caller (a temporary/literal is changed - how could
> someone possibly want that?).

The change may well be visible...

class C{
    int x;
}
struct W{
    C c;
}

W createW(C c){ return W(c); }
void foo(ref W w){ w.c.x = 2; }

void main(){
    C c = new C;
    assert(c.x==0);
    foo(createW(c));
    assert(c.x==2);
}
November 07, 2012
Re: Const ref and rvalues again...
On Wednesday, 7 November 2012 at 10:33:03 UTC, Timon Gehr wrote:
> You are still missing that const in C++ is different from const 
> in D. Also, if the point is to have higher speed, why shouldn't 
> the function be allowed to use an rvalue as scratch space 
> without a _deep copy_ ?
>
> const in C++ does not mean anything. It is just loosely 
> enforced interface documentation. const in D actually restricts 
> what the callee can do with the argument, in a transitive 
> fashion.

 Also depending on how you think of it, the const ref in C++ 
logically it didn't make sense to pass a mutable rvalue; Say you 
pass the number 3, you can't modify it logically (ie you can't 
redefine PI afterall).

> When my struct does not support any operations that are const, 
> and creating a mutable copy is not possible due to indirections?
>
>> I would simply continue to disallow that, since that would 
>> mean that changes to the referenced rvalue would not be 
>> visible for the caller (a temporary/literal is changed - how 
>> could someone possibly want that?).
>
> The change may well be visible...
>
> class C{
>     int x;
> }
> struct W{
>     C c;
> }
>
> W createW(C c){ return W(c); }
> void foo(ref W w){ w.c.x = 2; }
>
> void main(){
>     C c = new C;
>     assert(c.x==0);
>     foo(createW(c));
>     assert(c.x==2);
> }

 So basically being named (or not) doesn't change the data, what 
it is or how it's used, only how accessible it is.

 There's some forms of techniques that don't seem to have any 
value until you have a chance to think about it and use them; 
Like scopes, overloading, referencing, Exceptions you can easily 
do without them all (We have C afterall), but it isn't as much 
fun or clean. Some features and uses thereof make more sense for 
the back/private implementation rather than the public interface.

 The reason I gave my 'divide' example is because a while back I 
wrote a BitInt (in C like 10 years ago, and a bit buggy); The 
divide function actually calculated the remainder as a side 
effect, and using wrappers or writing them as separate portions 
would actually be harder and slower than joined as it was.
November 07, 2012
Re: Const ref and rvalues again...
On Wednesday, 7 November 2012 at 06:13:25 UTC, Rob T wrote:
> ref T works fine, but if you wish to use f( ref T a ) on a temp 
> value returned from another function call, you'll need to 
> overload f() to pass by value, which means creating a duplicate.

Duplicating the function, yes, but not duplicating the rvalue 
since it is moved in D. Isn't this analog to the C++ solution?

C++:
void f(T& a) { // for lvalues
    this->resource = a.resource;
    a.resetResource();
}
void f(T&& a) { // for rvalues (moved)
    this->resource = a.resource;
    a.resetResource();
}

D:
void f(ref T a) { // for lvalues
    this.resource = a.resource;
    a.resetResource();
}
void f(T a) { // rvalue argument is not copied, but moved
    this.resource = a.resource;
    a.resetResource();
}

T g() {
    T temp;
    temp.createResource();
    return temp; // D already moves temp!
                 // 'Named Return Value Optimization'
}

> I tried f( move(g()) ) but that fails to work. My best guess is 
> that D does a hidden move of the temp instead of a copy to 
> value. I can't say for sure because the documentation is not 
> clear and is missing important details like this. I also cannot 
> rely on clever compiler optimizations that may or may not be 
> implemented as a guarantee.

You could implement a copy constructor 'this(this)' in your 
struct T and see when it is invoked to check when an instance is 
actually copied. I'd expect that invoking 'f(g());' with above 
implementation doesn't copy anything.
November 07, 2012
Re: Const ref and rvalues again...
On Wednesday, 7 November 2012 at 10:33:03 UTC, Timon Gehr wrote:
> You are still missing that const in C++ is different from const 
> in D.
> const in C++ does not mean anything. It is just loosely 
> enforced interface documentation. const in D actually restricts 
> what the callee can do with the argument, in a transitive 
> fashion.

I still don't get the big difference (except for transitiveness 
for pointers). Given a const reference, I'm only able to invoke 
methods decorated with the const keyword (or inout in D) keyword, 
both in C++ and D. And I can only pass it as argument by ref to 
functions which do not alter it (also taking a const reference, 
that is).

> Also, if the point is to have higher speed, why shouldn't the 
> function be allowed to use an rvalue as scratch space without a 
> _deep copy_ ?

I'm sorry but I don't get what you mean here. Could you please 
elaborate on this?

> When my struct does not support any operations that are const, 
> and creating a mutable copy is not possible due to indirections?

If your struct doesn't support any const operations, it most 
likely has good reasons not to.

> The change may well be visible...

True in this case, but only if you know exactly what foo() does 
when you call it inside the main() function. And that is probably 
an indicator for bad encapsulation - most of the time, you 
shouldn't have the knowledge how foo() is exactly implemented 
when using it from the outside.
It is clear that there are some examples where you want to pass 
an rvalue argument to a mutable ref parameter if you know exactly 
what the function does. But imo these cases are very rare and I 
don't really regard it as big issue if you need to add a line 
'auto tmp = myRvalue;' before the function call to transform it 
to a referenceable lvalue, in these few cases.
Much more commonly, you need a parameter just as read-only input. 
Consider a real-word-example of a 4x4 matrix consisting of 16 
doubles (128 bytes). Most of the time, you'd only need a 
read-only input instance when working with it (combining 
matrices, transforming vectors etc.). Given its size (it's not 
really huge, I acknowledge that ;)), you probably want to avoid 
copying it around and therefore pass it by ref, but want that to 
also work for rvalues (produced by matrix combinations like 
'viewMatrix * modelMatrix', for example).

struct Matrix
{
    double[16] data;

    // this op= other
    ref Matrix opOpAssign(string op)(in ref Matrix other);

    // Matrix result = this op other
    Matrix opBinary(string op)(in ref Matrix other) const;

    // double4 result = this * vector
    // the vector (32 bytes) may be passed by value for AVX
    double4 opBinary(string op)(in ref double4 vector) const
        if (op == "*");
};
2 3 4 5 6 7 8 9 10
Top | Discussion index | About this forum | D home