Thread overview
rvalue arguments passable as const references
Oct 09, 2012
martin
GC is reclaiming live object
Oct 10, 2012
deadalnix
Oct 10, 2012
deadalnix
October 09, 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



October 10, 2012
And I don't use C stuff in the application. I don't explicitly free anything and use the last version of D.

Obviously, the problem isn't easy to reproduce in a small program, But I get consistent crash in some test cases.

So, What am I supposed to do now to investigate the issue ?
October 10, 2012
Le 10/10/2012 18:22, deadalnix a écrit :
> And I don't use C stuff in the application. I don't explicitly free
> anything and use the last version of D.
>
> Obviously, the problem isn't easy to reproduce in a small program, But I
> get consistent crash in some test cases.
>
> So, What am I supposed to do now to investigate the issue ?

Sorry for that message, it seems that I posted it as an answer when it wasn't intended to be one.