rvalue arguments passable as const references

martin kinke at libero.it
Tue Oct 9 07:29:25 PDT 2012


Hi there,

I'd like to propose an implicit rvalue-to-const-ref conversion. 
Basically, we have 5 categories of function parameters:

void foo(T)(T copy, in T constCopy, ref T reference, in ref T 
constReference, out T result) { ... }
// "in ref T constReference" is equivalent to "ref const(T) 
constReference"

copy: pass the argument by value as copy on the stack so that the 
function has its own private, mutable copy
constCopy: pass by value too, but the copy will not be modified 
(useful to prevent accidental modification)
reference: pass the lvalue argument by reference, i.e., pass a 
hidden pointer, so that the function can modify the original 
argument
constReference: pass by reference too, but the original argument 
will not be modified
result: pass by reference too; the original argument will be set 
(for additional return values etc.)

The "reference" and "result" cases are straight-forward. The 
default "copy" case is useful for the following scenario:

T get(uint index)
{
     index = min(index, _length - 1u); // prevent out-of-bounds 
index
     return _data[index];
}

because if the "index" parameter was const, the function would 
need an additional private variable (such as "safeIndex"), 
polluting the function's namespace.

But the interesting thing are the related "constCopy" and 
"constReference" cases. They both mean that the parameter is only 
needed for reading and will not be touched. In the "constCopy" 
case, a copy of the argument is used, whereas in the 
"constReference" case, the original argument is used by passing a 
hidden pointer. So the latter is more efficient in case the type 
T is a larger struct or a struct with non-trivial copy 
constructor because a copy is elided. The fact "&constCopy != 
&constReference" may lead to tricky aliasing issues though:

int bar(ref int dst, in ref int src)
{
     dst = 2*src;
     return src;
}
int bar2(ref int dst, in int src)
{
     dst = 2*src;
     return src;
}
int i = 1;
assert(bar2(i, i) == 1 && i == 2);
i = 1;
assert(bar(i, i)  == 2 && i == 2); // the const src parameter is 
modified since the original argument i is also used as mutable 
dst parameter!

So a transparent pass-by-ref for const parameters and suited 
structs is unfortunately problematic.

The current usability problem is that in the "constReference" 
case, only lvalue arguments can be used. That doesn't really make 
sense in my opinion - why should we not be allowed to pass 
rvalues efficiently by reference if they are not modified?

int src, dst;
bar(dst, src);           // works
bar(dst, 2);             // does not compile: 2 is a literal 
(rvalue)
                          // clumsy solution: immutable tmp = 2; 
bar(dst, tmp);
bar(dst, bar(dst, src)); // does not compile: nested bar() 
returns an intermediate (rvalue)
                          // clumsy solution: immutable tmp = 
bar(dst, src); bar(dst, tmp);

I'm convinced that if const (in) is used properly (a good coding 
practice anyway), we do not need a discussion about allowing 
conversions of rvalues to mutable-refs or literals being treated 
as lvalues. Mutable references should only be used for parameters 
if the argument may be modified during the function call, and I 
think it's good to disallow rvalues (intermediates and literals) 
here, so that the optional modifications are guaranteed to be 
visible for the caller. But if the argument is only used for 
reading, rvalues should definitely be allowed for higher coding 
comfort.

As to the potential escaping of references (references to values 
which have gone out of scope), I think that it falls into the 
responsibility of the developer, and preventing the address-of 
operator & for references is way too strict - you may just need 
it to determine if there is an aliasing issue or to pass the 
address to external C/C++ code (inside the function body).

Just my 2 cents to the whole rvalue => ref discussion...

Martin





More information about the Digitalmars-d mailing list