digitalmars.D.learn - meaning of "auto ref const"?
- Picaud Vincent (21/21) Dec 18 2016 Reading std/bigint.d code:
- Basile B. (23/32) Dec 18 2016 With auto ref, the parameter can be either a LValue or a RValue.
- Picaud Vincent (4/7) Dec 18 2016 Thank you for your detailed answer, things are perfectly clear
- kinke (12/25) Dec 18 2016 See https://dlang.org/spec/template.html#auto-ref-parameters.
- Picaud Vincent (3/9) Dec 18 2016 Thank you for your complementary answer and explanation. All
- =?UTF-8?Q?Ali_=c3=87ehreli?= (38/38) Dec 20 2016 As a general rule, 'auto ref' should probably be const. If the purpose
- Picaud Vincent (3/16) Dec 20 2016 Thank you Ali! This is effectively a trap I had not realized, you
- =?UTF-8?Q?Ali_=c3=87ehreli?= (93/96) Dec 20 2016 If it means "rvalue reference"[1], then there is no equivalent is D
- Picaud Vincent (10/40) Dec 20 2016 Ok, this one is quite intuitive.
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
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
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
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
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
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
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
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
On Tuesday, 20 December 2016 at 20:08:32 UTC, Ali Çehreli wrote:If the purpose is optimization, the good news areYes it is :)* Classes are already reference types so there is no lvalue or rvalue reference distinction thereOk, 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. :pThank you for your time and these valuable explanations, I learnt a lot. --Vincent
Dec 20 2016