November 07, 2012
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
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
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
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
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
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
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
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
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
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 == "*");
};
1 2 3 4 5 6 7 8 9 10 11
Top | Discussion index | About this forum | D home