digitalmars.D - Regarding ref results
- bearophile (100/100) Apr 30 2014 Below you can find some musings, that perhaps just show my
- John Colvin (45/52) Apr 30 2014 Assuming a static array is considered to be a normal aggregate,
Below you can find some musings, that perhaps just show my ignorance of C++11. The topic is partially related to the && of C++11: http://en.wikipedia.org/wiki/C%2B%2B11#Rvalue_references_and_move_constructors Sometimes I have to compute hash digests, or to return small but fixed amounts of data in array for various reasons. There are many ways to do it in D, a simple way is to return an ubyte[], but I like static safety (to avoid empty arrays, null pointers, etc), this is a simple solution: import std.stdio: writefln; import std.string: representation; ubyte[20] sillyHash(in ubyte[] key) pure nothrow nogc { typeof(return) result; foreach (immutable i, ref r; result) r = (key[i % $] ^ i) % 256; return result; } void main() { immutable data = "some text"; immutable digest = data.representation.sillyHash; writefln("%-(%02x%)", digest); } This first solution performs a copy of the output array. (In some cases a compiler can reserve the array space in the stack frame of the caller and just return a pointer, return value optimization). This solution is clean, safe, easy to use, and if your function is not called many times and the output array is not too much large, it's a solution I like. Copying small array a small number of times is cheap enough. ------------------------- If the output array is larger, or you need to call that function many times and you don't want to pay the copy, you can use references (you can also use pointers, but they are less safe): import std.stdio: writefln; import std.string: representation; import std.traits: ParameterTypeTuple, Unqual; void sillyHash2(in ubyte[] key, ref ubyte[20] digest) pure nothrow nogc { foreach (immutable i, ref r; digest) r = (key[i % $] ^ i) % 256; } void main() { immutable data = "some text"; Unqual!(ParameterTypeTuple!sillyHash2[1]) digest; sillyHash2(data.representation, digest); writefln("%-(%02x%)", digest); } This second version is almost equally safe, but it has some disadvantages: - You need to define the return variable before the function, this is not handy for UFCS chains; - You also need to define the type of the result before calling the function. Here I have found such type using ParameterTypeTuple, but this is not so handy, and if the function becomes a template, you also need to instantiate it to find the argument type. - Now the digest variable can't be immutable. - The code at the call point is longer. ------------------------- A hypothetical version that combines the advantages of the first two versions: import std.stdio: writefln; import std.string: representation; ref ubyte[20] sillyHash3(ref return digest, in ubyte[] key) pure nothrow nogc { foreach (immutable i, ref r; digest) r = (key[i % $] ^ i) % 256; return digest; } void main() { immutable data = "some text"; immutable digest = data.representation.sillyHash3; writefln("%-(%02x%)", digest); } sillyHash3 has two arguments, but the programmer can only give the second (all the arguments past the first), the first argument is handled by the compiler. swapped function arguments, 'digest' is the first). Despite this syntax idea is not very good, it has the advantage of being efficient (only a pointer is returned, no array copy), it's safe, it's clean at the call point, allows to tag the result as immutable, and it doesn't rely on compiler optimizations like is tiny). The same syntax is also usable for structs: struct Foo { /*many fields here*/ } ref Foo bar(ref return f1) { // Some computation here, // and f1 fields initialization. return f1; } void main() { immutable Foo f = bar(); } Bye, bearophile
Apr 30 2014
On Wednesday, 30 April 2014 at 10:22:45 UTC, bearophile wrote:- You need to define the return variable before the function, this is not handy for UFCS chains; - You also need to define the type of the result before calling the function. Here I have found such type using ParameterTypeTuple, but this is not so handy, and if the function becomes a template, you also need to instantiate it to find the argument type.Assuming a static array is considered to be a normal aggregate, the System V ABI defines some of this problem away. int[10] foo() { int[10] a; //fill a return a; } void bar() { foo(); } becomes assembly equivalent to this, assuming a sensible (not necessarily very clever) compiler: void foo(int* ret) { int[10] a; //fill a ret[0 .. 10] = a[]; } void bar() { int[10] a; foo(a.ptr); } The memory is allocated on stack of caller, and then it passes the base address in %edi Perhaps I'm being naive, but it should be trivial for the optimiser to produce something like: void foo(int* ret) { ret[0 .. 10] = 0; //pessimistic, sometimes elidable.* //fill ret as if it was an int[10] } void bar() { int[10] a; foo(a.ptr); } *especially when inlined. I'm not so familiar with other ABIs, but there are really only 3 choices: 1) allocate in caller, 2) memcopy or 3) return in multiple registers. I doubt anyone uses 2, 1 is what we see above and 3 is very cheap (register moves normally cost little).
Apr 30 2014