On Friday, 8 February 2013 at 21:06:27 UTC, Namespace wrote:
I've been thinking about the lack of rvalue references.
Therefore, I would like to know what you think is the best
workaround for this feature.
I illustrate below the only (*) five solutions briefly and
summarized.
What do you think is the best? Which of these do you use? Or
did I forget some possible solutions?
I'm looking for this for small data structures, such as
vectors, matrices, or colors.
(*) In my opinion.
--------
struct A { }
---- Solution #1 ----
// rvalues
void foo(A a) {
foo(a);
}
// lvalues
void foo(ref A a) {
}
----------------------------------
Summarized: flexible, efficient but code bloat, more effort and
bug prone:
void foo(A a) {
writeln("call no ref");
foo(a);
}
void foo(const ref A a) {
writeln("call ref");
}
void main() {
foo(A());
}
-> endless loop!
---- Solution #2 ----
// fuck it, copy lvalues and rvalues
void foo(A a) {
}
----------------------------------
Summarized: flexible, no code bloat but inefficient ->
unnecessary copies
---- Solution #3 ----
// allow only lvalues
void foo(ref A a) {
}
----------------------------------
Summarized: You have to make temporary values by yourself:
unhandy, ugly and code bloat
---- Solution #4 ----
// accept only pointers
void foo(A* a) {
}
----------------------------------
Summarized: C Style, nullable and same problems as with
solutions #3.
---- Solution #5 ----
// Use classes
class A { }
// Works for lvalues and rvalues
void foo(A a) {
}
----------------------------------
Summarized: flexible, efficient, no code bloat and same
creation (with static opCall) as structs. But: heap allocations
and nullable.
Honestly, I'd go with option 1. With a mixin template, it should
be pretty to not mess up too hard.
Also, In regards to multi-args, I'd just bite the bullet, and
make it so that if a single arg is rvalue, then all args are
rvalue. Not perfect, but I think it is a good cost to gain ratio.
Here is a mixin template that *almost* does it:
//----
import std.stdio;
import std.conv;
import std.traits;
template rvalue(alias fun, string funs)
{
private string ss()
{
//enum funs = fun.stringof; Don't know how to get
function name :(
enum Ret = ReturnType!fun.stringof;
alias Args = ParameterTypeTuple!fun;
alias ParameterStorageClassTuple!fun pstc;
enum names = [ParameterIdentifierTuple!fun];
string s;
s ~= Ret ~ " " ~ funs ~ "(";
foreach(i, Type; Args[0 .. $])
{
if (pstc[i] == ParameterStorageClass.scope_) s ~=
"scope ";
if (pstc[i] == ParameterStorageClass.out_) s ~=
"out ";
if (pstc[i] == ParameterStorageClass.lazy_) s ~=
"lazy ";
//if (pstc[i] == ParameterStorageClass.ref_) s ~=
"ref "; //Commented: That's the whole point ;)
s ~= Args[i].stringof ~ " ";
s ~= names[i];
if (i + 1 != Args.length) s ~= ", ";
}
s ~= ")";
//TODO: print the FunctionAttribute
s ~= "\n{\n return " ~ funs ~ "(";
if (Args.length)
{
s ~= names[0];
foreach(i, Type; Args[1 .. $])
{
s ~= ", " ~ names[i + 1];
}
}
s ~= ");\n}\n";
return s;
}
enum rvalue = ss();
}
void foo(ref int a, ref const int b, scope int c)
{
writefln("%s %s %s", a, b, c);
}
mixin(rvalue!(foo, "foo"));
void main()
{
foo(1, 2, 3);
writeln(rvalue!(foo, "foo"));
}
//----
1 2 3
void foo(int a, const(int) b, scope int c)
{
return foo(a, b, c);
}
//----
See? pretty nice. The only thing missing is actually getting the
function name :/ I'm not sure how to do it, so I passed two
arguments, but it should be doable with just 1 (worst case
scenario, you pass just the string).
Also, I didn't code the FunctionAttribute part, but that's just
because it's late and I want to sleep ;) It should be just about
the same as with ParameterStorageClass.
You *could* further improve on this design, if you so felt like
it, so that it generates all combination of ref/non ref
parameters. That's more complicated. But doable.