Thread overview
meaning of "auto ref const"?
Dec 18, 2016
Picaud Vincent
Dec 18, 2016
Basile B.
Dec 18, 2016
Picaud Vincent
Dec 18, 2016
kinke
Dec 18, 2016
Picaud Vincent
Dec 20, 2016
Ali Çehreli
Dec 20, 2016
Picaud Vincent
Dec 20, 2016
Ali Çehreli
Dec 20, 2016
Picaud Vincent
December 18, 2016
Reading std/bigint.d code:

https://github.com/dlang/phobos/blob/00c1cc3b0d354363793c8b419ce84da722578138/std/bigint.d#L589

I have seen this:

bool opEquals()(auto ref const BigInt y) const pure @nogc
{
   return sign == y.sign && y.data == data;
}

my problem is that I do not understand the role/meaning of "auto" in this context.

Moreover in the opCmp code, "auto" is not present anymore, which is an extra source of confusions for me.

int opCmp(ref const BigInt y) pure nothrow @nogc const
{
   // Simply redirect to the "real" opCmp implementation.
   return this.opCmp!BigInt(y);
}

What is the rational?

-----------------

Another interrogation for me, who come from C++, is how to translate into D:

template<typename T> void foo(T&& t);


December 18, 2016
On Sunday, 18 December 2016 at 13:14:08 UTC, Picaud Vincent wrote:
> Reading std/bigint.d code:
>
> https://github.com/dlang/phobos/blob/00c1cc3b0d354363793c8b419ce84da722578138/std/bigint.d#L589
>
> I have seen this:
>
> bool opEquals()(auto ref const BigInt y) const pure @nogc
> {
>    return sign == y.sign && y.data == data;
> }
>
> my problem is that I do not understand the role/meaning of "auto" in this context.


With auto ref, the parameter can be either a LValue or a RValue.
When passing a struct as auto ref, it's taken as ref.
for example:

struct Foo{this(this){writeln("copy");}}
struct Bar{@disable this(this);}
void testAsRef(T)(ref T t){}
void testAsValue(T)(T t){}
void testRefOrValue(T)(auto ref T t){}

Foo foo;
Bar bar;

testAsRef(1); // error 1 is not ref
testAsRef(foo); // ok, not copied
testAsRef(bar); // ok, not copied

testAsValue(1); // ok
testAsValue(foo); // ok but copied
testAsValue(bar); // error, could only be copied but postblit is disabled

testRefOrValue(1); // ok, not taken as ref
testRefOrValue(foo); // ok, not copied
testRefOrValue(bar); // ok, taken as ref

As you can see, auto ref is more flexible with the parameter. This make sense for templated functions.
December 18, 2016
On Sunday, 18 December 2016 at 13:14:08 UTC, Picaud Vincent wrote:
> bool opEquals()(auto ref const BigInt y) const pure @nogc
> {
>    return sign == y.sign && y.data == data;
> }
>
> my problem is that I do not understand the role/meaning of "auto" in this context.

See https://dlang.org/spec/template.html#auto-ref-parameters. It's used to end up with an `opEquals(ref const BigInt y)` for lvalue args (passed by reference) and with an `opEquals(const BigInt y)` for rvalue args (passed by value => implicitly moved in D (as they are rvalues)).

> Moreover in the opCmp code, "auto" is not present anymore, which is an extra source of confusions for me.
>
> int opCmp(ref const BigInt y) pure nothrow @nogc const
> {
>    // Simply redirect to the "real" opCmp implementation.
>    return this.opCmp!BigInt(y);
> }

TypeInfo_Struct apparently requires (or used to require) an `int opCmp(ref const T rhs)` overload, i.e., a version taking the rhs lvalue argument by reference (see https://dlang.org/spec/operatoroverloading.html#compare). Note that there are other overloads afterwards which take the rhs argument by value, thereby allowing rhs rvalues too.
December 18, 2016
On Sunday, 18 December 2016 at 14:25:04 UTC, Basile B. wrote:
> ...
> As you can see, auto ref is more flexible with the parameter. This make sense for templated functions.

Thank you for your detailed answer, things are perfectly clear now. Also sorry for the doc linksI should have found it before asking my question.
December 18, 2016
On Sunday, 18 December 2016 at 14:32:08 UTC, kinke wrote:

> TypeInfo_Struct apparently requires (or used to require) an `int opCmp(ref const T rhs)` overload, i.e., a version taking the rhs lvalue argument by reference (see https://dlang.org/spec/operatoroverloading.html#compare). Note that there are other overloads afterwards which take the rhs argument by value, thereby allowing rhs rvalues too.

Thank you for your complementary answer and explanation. All these look less strange to me now.
December 20, 2016
As a general rule, 'auto ref' should probably be const. If the purpose of 'ref' is so that the argument would be mutated, then allowing a copy of an rvalue to this function could very well be a bug:

struct S {
    int i;
}

void foo()(auto ref S s) {
    s.i = 42;  // <-- Cannot be observed if the arg is rvalue
}

void main() {
    foo(S(1));
}

To contradict myself (and I hate when I do that! :p), the function may be using the rvalue in a non-const context, which would make the mutation observable:

struct S {
    int i;

    void sayIt() {
        import std.stdio;
        writeln(i);
    }
}

void foo()(auto ref S s) {
    s.i = 42;
    s.sayIt();    // <-- Here
}

// ...

Another one through the return value (but this time it's a copy anyway, perhaps defeating the 'ref' purpose):

// ...

S foo()(auto ref S s) {
    s.i = 42;
    return s;
}

void main() {
    foo(S(1)).sayIt();  // <-- Here
}

Ali

December 20, 2016
On 12/18/2016 05:14 AM, Picaud Vincent wrote:

> Another interrogation for me, who come from C++, is how to translate
> into D:
>
> template<typename T> void foo(T&& t);

If it means "rvalue reference"[1], then there is no equivalent is D because D does not allow references to rvalues, even if const.

If the purpose is optimization, the good news are

* Classes are already reference types so there is no lvalue or rvalue reference distinction there

* rvalue structs are automatically moved to functions when passed by-copy

import std.stdio;

struct S {
    double i;
    ubyte[1000] buf;
    this(int i) {
        this.i = i;
        writefln("constructor       %s", i);
    }
    this(this) {
        writef(  "post-blit         %s -> ", i);
        this.i += 0.1;
        this.buf = buf.dup;
        writeln(i);
    }
    ~this() {
        writefln("destructor for    %s", i);
    }
}

void foo(S s) {
    writefln(    "foo(by-copy)      %s", s.i);
}

void foo(ref const(S) s) {
    writefln(    "foo(by-ref-const) %s", s.i);
}

// UNCOMMENT THIS TO BE SURPRISED:
// void foo(ref S s) {
//     writefln(    "foo(by-ref)       %s", s.i);
// }

void main() {
    {
        writeln("\n--- rvalue ---");
        foo(S(1));
    }
    {
        writeln("\n--- mutable lvalue ---");
        auto s = S(2);
        foo(s);
    }
    {
        writeln("\n--- const lvalue ---");
        const s = S(3);
        foo(s);
    }
}

According to the output, there is no post-blit executed for the rvalue:

--- rvalue ---
constructor       1
foo(by-copy)      1
destructor for    1

--- mutable lvalue ---
constructor       2
post-blit         2 -> 2.1
foo(by-copy)      2.1
destructor for    2.1
destructor for    2

--- const lvalue ---
constructor       3
foo(by-ref-const) 3
destructor for    3

There is a surprising difference in D:

* First, in C++, you cannot have both the by-copy and by-ref-to-const overload of a function: It would be ambiguous for rvalues.

* You can have that in D, which brings the interesting difference:

In D, non-constness of an object seems to be more important in overload resolution: Notice how mutable lvalue above is passed to by-copy instead of the potentially-more-optimal by-const-ref above. D realizes that a mutable object is for mutation and because by-const-ref cannot mutate it, D passes it to the by-copy function. (This may be seen as a bug by some.)

Interestingly, enabling the by-mutable-ref overload above, now the mutable object goes to by-ref and there is no automatic copy:

--- rvalue ---
constructor       1
foo(by-copy)      1
destructor for    1

--- mutable lvalue ---
constructor       2
foo(by-ref)       2
destructor for    2

--- const lvalue ---
constructor       3
foo(by-ref-const) 3
destructor for    3

Ali

[1] I have an issue with "rvalue reference" as rvalue references can be references to lvalues as well. :p

December 20, 2016
On Tuesday, 20 December 2016 at 19:24:32 UTC, Ali Çehreli wrote:
> As a general rule, 'auto ref' should probably be const. If the purpose of 'ref' is so that the argument would be mutated, then allowing a copy of an rvalue to this function could very well be a bug:
>
> struct S {
>     int i;
> }
>
> void foo()(auto ref S s) {
>     s.i = 42;  // <-- Cannot be observed if the arg is rvalue
> }
>
> void main() {
>     foo(S(1));
> }

Thank you Ali! This is effectively a trap I had not realized, you probably save me from some long debugging time.
December 20, 2016
On Tuesday, 20 December 2016 at 20:08:32 UTC, Ali Çehreli wrote:

> If the purpose is optimization, the good news are

Yes it is :)

> * Classes are already reference types so there is no lvalue or rvalue reference distinction there

Ok, this one is quite intuitive.

> import std.stdio;
> ...

Thank you for the illustrative example, I have reproduced it.

> There is a surprising difference in D:
>
> In D, non-constness of an object seems to be more important in overload resolution: Notice how mutable lvalue above is passed to by-copy instead of the potentially-more-optimal by-const-ref above. D realizes that a mutable object is for mutation and because by-const-ref cannot mutate it, D passes it to the by-copy function. (This may be seen as a bug by some.)

Thank you for pointing out this. I was not aware of that, and for sure this is not the C++  behavior.

> Interestingly, enabling the by-mutable-ref overload above, now the mutable object goes to by-ref and there is no automatic copy:

Ok, that is "moral" and without surprise.

> --- rvalue ---
> constructor       1
> foo(by-copy)      1
> destructor for    1
>
> --- mutable lvalue ---
> constructor       2
> foo(by-ref)       2
> destructor for    2
>
> --- const lvalue ---
> constructor       3
> foo(by-ref-const) 3
> destructor for    3
>
> Ali
>
> [1] I have an issue with "rvalue reference" as rvalue references can be references to lvalues as well. :p

Thank you for your time and these valuable explanations, I learnt a lot.
--Vincent