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