www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Regarding ref results

reply "bearophile" <bearophileHUGS lycos.com> writes:
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:


// Program#1
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):


// Program#2
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:


// Program#3
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.

The compiler rewrites this sillyHash3 like Program#2 (but with 
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 
Program#1 so it's reliably efficient (unless the returned array 
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
parent "John Colvin" <john.loughran.colvin gmail.com> writes:
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