www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - meaning of "auto ref const"?

reply Picaud Vincent <picaud.vincent gmail.com> writes:
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);
Dec 18 2016
next sibling parent reply Basile B. <b2.temp gmx.com> writes:
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.
Dec 18 2016
parent Picaud Vincent <picaud.vincent gmail.com> writes:
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.
Dec 18 2016
prev sibling next sibling parent reply kinke <noone nowhere.com> writes:
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.
Dec 18 2016
parent Picaud Vincent <picaud.vincent gmail.com> writes:
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.
Dec 18 2016
prev sibling next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
Dec 20 2016
parent Picaud Vincent <picaud.vincent gmail.com> writes:
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.
Dec 20 2016
prev sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
Dec 20 2016
parent Picaud Vincent <picaud.vincent gmail.com> writes:
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
Dec 20 2016