rvalues -> ref (yup... again!)

Manu turkeyman at gmail.com
Tue Mar 27 18:14:18 UTC 2018


On 27 March 2018 at 00:14, Atila Neves via Digitalmars-d
<digitalmars-d at puremagic.com> wrote:
> On Monday, 26 March 2018 at 19:24:13 UTC, Manu wrote:
>>
>> On 26 March 2018 at 07:40, Atila Neves via Digitalmars-d
>> <digitalmars-d at puremagic.com> wrote:
>>>
>>> On Friday, 23 March 2018 at 22:01:44 UTC, Manu wrote:
>>>>
>>>>
>>>> Forked from the x^^y thread...
>>>> <snip>
>>>
>>>
>>>
>>> C++ T&& (forwarding reference) -> D auto ref T
>>> C++ T&& (Rvalue reference) -> D T
>>> C++ const T& -> D T
>>
>>
>> Yeah, no... T may be big. Copying a large thing sucks. Memory copying
>> is the slowest thing computers can do.
>
>
> That's _if_ T is big and _if_ it even gets copied,

You've just described the exact anatomy of a ref function!
You wouldn't write a function to receive T by ref UNLESS T was both
big, and the function probably won't inline (therefore definitely
copy), and that condition will be triggered by any of the list of
reasons I've said a bunch of times (extern, dll, lib, virtual, etc).
People don't just love writing ref (well, some people might), but they
use it deliberately, typically in user-facing boundary API's for these
exact reasons.


> the combination of which
> I think happens very rarely. When that happens I think that the temporary
> isn't a big deal. This code:
>
> struct Foo { int[1024] data; }
> int byValue(Foo f) { return f.data[42]; }
>
> Generates this assembly (ldc2 -O3, clang does the same for C++):
>
> 0000000000000000 <_D3foo7byValueFSQo3FooZi>:
>    0:   8b 84 24 b0 00 00 00    mov    eax,DWORD PTR [rsp+0xb0]
>    7:   c3                      ret
>
> And I wrote a type for which a move and a copy are the same on purpose: in
> "real life" it's more likely that the memory will be dynamically allocated,
> probably held in a slice, and moved instead.
>
> Are there cases in which there will be an expensive copy? Yes, then pass by
> ref/pointer. But measure first, and prefer to pass by value by default.

Right. I'm talking about deliberate use of ref... Or *existing*
(deliberate) use of ref, as is the case in almost all my my cases. The
code already exists.
As you assess, use of ref in D is fairly rare. I've been looking for
cases where other people are inconvenienced by this... hard to find.
I suspect there are reasons for this. One of them is that this
inconvenience suppresses it; ie, you will choose not to use a ref even
when you might prefer to. Others include the fact that extern(C++)
isn't super popular, DLL's aren't popular, closed-source distributed
code is non popular, OOP is not popular, etc.


> It's different in C++ - stick a std::vector or std::string in your struct
> and passing by value (usually) copies the dynamically allocated memory. In D
> it moves.

Only if you ARE moving, and not copying. D must deep copy too if you
actually copy.
Your example assumes C++ doesn't have a move constructor. D has
implicit move semantics, so you can only make an equivalent comparison
where C++ also defines the move constructor so the move case doesn't
pollute the ref comparison.
Also, irrespective of whether move semantics are performed (eliding
potential deep copying, as in your example), the binary memcpy still
has to be performed when handling values by-val, unless RVO (we're not
talking about return values), or inlining is able to eliminate it.
Also, we're not talking about move semantics!


>> As an API author, exactly as in C++, you will make a judgement on a
>> case-by-case basis on this matter. It may be by-value, it may be by
>> const-ref. It depends on a bunch of things, and they are points for
>> consideration by the API author, not the user.
>
>
> You can still do that in D. There well may be a reason to pass by const ref.
> I'm just saying that there aren't that many, and in that in those rare cases
> a temporary is fine. Especially if the alternative are rvalue references.

We're not talking about rvalue references, we're talking about normal
references >_<


>> He's trying to say that C++ introduced rvalue references because normal
>> references weren't able to allow for move semantics to exist. It's a
>> red-herring. D already has move semantics, they work well, and they're not
>> on trial here.
>>
>> In C++'s case, it's not that references were deficient at being
>> references that C++ needed rval-references, it's that references were
>> deficient at being move-able.
>
>
> There were deficient at being moveable because temporaries can bind to const
> T&.

... what? That's just not true at all.
If temporaries couldn't bind to C++ ref, then you *definitely*
wouldn't be able to move it, because you can guarantee that someone
else owns the reference. You can't move references, under any
circumstances, in either language... and that's actually the whole
point of references.

rvalue references were introduced in C++ to capture the calls with
rvalues into a separate function call, exactly the same way as the
by-value overload will catch the rvalues in D (and perform an implicit
move).
It was impossible for C++ to implement D's implicit move semantics
when receiving by-value for reasons that have nothing to do with
references; C++ allows interior pointers which breaks implicit moving,
C++ can overload default constructor which also breaks implicit moving
(no implicit way to reset the state of the prior owner).
That's the reason that C++ was forced to introduce rvalue references,
rather than implement implicit move logic like in D.

It was the choice of D to ban interior pointers, and dis-allow
overloading the default struct constructor which supports D's implicit
move semantics. If D didn't have those 2 things, it would need
rvalue-ref's the same as C++ for the same reasons. References have no
interaction with move semantics.

But again, we're not talking about move semantics here, we're just
talking about references ;)


>> That is not a problem that exists in D.
>
>
> Because temporaries can't bind to ref const(T).

No. This truly has nothing to do with it.
rvalues would continue to choose the by-val function, performing a
move whenever possible. All existing rules are correct as they are.


>> It's fine for references to
>> just be references in D. We're not struggling to make references
>> move-able in D, that's not a thing, we already have move semantics.
>> Any extension of this conversation about references into C++
>> rvalue-references (T&&) and or move-semantics are red-herrings.
>> There's no such problem in D that needs to be resolved, and the
>> existing solution is excellent.
>
>
> If I'm reading you correctly (which I might not), you seem to be saying that
> there's a way forward in which:
>
> 1) D's move semantics aren't affected
> 2) No rvalue references are introduced
> 3) Temporaries can bind to ref const(T)
>
> I'd love to know what that would look like.

That's exactly what I've been saying. For like, 9 years..
It looks like this:
https://github.com/TurkeyMan/DIPs/blob/ref_args/DIPs/DIP1xxx-rval_to_ref.md
 (contribution appreciated)

As far as I can tell, it's completely benign, it just eliminates the
annoying edge cases when interacting with functions that take
arguments by ref. There's no spill-over affect anywhere that I'm aware
of, and if you can find a single wart, I definitely want to know about
it.
I've asked so many times for a technical destruction, nobody will
present any opposition that is anything other than a rejection *in
principle*. This is a holy war, not a technical one. And as far as I
can tell, it basically only affects me, because I do so much work
against established C++ code! >_<


More information about the Digitalmars-d mailing list