DIP 1016 and const ref parameters

Jonathan M Davis newsgroup.d at jmdavisprog.com
Wed Jun 19 19:25:59 UTC 2019


On Wednesday, June 19, 2019 12:28:12 PM MDT XavierAP via Digitalmars-d-learn 
wrote:
> On Wednesday, 19 June 2019 at 12:55:09 UTC, Jonathan M Davis
>
> wrote:
> > Even in C++, using const ref is not as good a practice as it
> > once was, because they added move constructors, finally making
> > object moveable. The result is that in many cases, it's
> > actually more efficient to just copy values in C++ rather than
> > use const &, but which is better does depend on the code.
> >
> > As for D, unless you're dealing with large objects, odds are
> > that worrying about passing by value is pointless. D classes
> > are reference types, and D structs have move semantics
> > built-in. So, you don't get as many copies as you would in
> > C++98, and the situation is probably better than newer versions
> > of C++, since IIRC, C++ classes aren't moveable by default,
> > whereas D structs are. In general, you're probably better off
> > just passing by value unless you find that a particular piece
> > of code is inefficient when benchmarking. Either way, you don't
> > want to be slapping const on everything the way you would in
> > C++, because D's const is far more restrictive. So, while it
> > still can be quite useful, odds are that if you start using it
> > heavily, you're going to run into problems fast - especially
> > since casting away const and mutating an object is undefined
> > behavior in D. D's const has no back doors. If something is
> > const, then you can't mutate it unless you also have a mutable
> > reference to the same data. And because const is transitive,
> > you pretty much can't get mutable stuff from const stuff like
> > you frequently can in C++ (e.g. in C++, it's possible to have a
> > const container of mutable objects, wherein D, once part of
> > something is const, everything within that part is const).
> >
> > As for the DIP, I'd suggest watching Andrei's recent dconf talk
> > on the subject:
> >
> > https://www.youtube.com/watch?v=aRvu2JGGn6E&feature=youtu.be
> >
> > - Jonathan M Davis
>
> I am not talking about cases that would be candidate for moving,
> or where const would be any problem. If you want an example for
> the sake of argument:
>
> struct Matrix3D
> {
> Matrix3D opBinary(string op)(const ref Matrix3D rhs) const;
> }
> unittest
> {
>   auto a = Matrix3D.random;
>   assert(a == a * Matrix3D.identity);
>   assert(a == a + Matrix3D.zeros);
> }
>
> I did watch Andrei's talk, actually this is where I started and
> learned about the DIP(s), then I was confused that 1016 had been
> rejected, and smelling that it may be "reopening" I was not sure
> where I can find the "index" of DIPs under discussion or
> whatever... :)

The DIPs are here: https://github.com/dlang/DIPs

Aside from looking through the newsgroup/forum for discussions on DIPs,
that's pretty much all you're going to find on that. Andrei's talk is the
most up-to-date information that we have about this particular DIP.

> > IIRC, C++ classes aren't moveable by default, whereas D structs
> > are.
>
> What do you mean that structs are movable? I know about RVO (in
> both D and C++, supposedly guaranteed by all compilers in
> practice, but not by language spec -- why not D?), but what about
> passing up the stack as here?

I mean that the object is moved from one place in memory to another rather
than copied. There are cases where the compiler needs to take an object and
put it somewhere else. RVO may be one of those, though if I understand
correctly with RVO, it may just place the return value outside of the
function in the first place to avoid needing to move. But regardless of
whether RVO causes a move, there are cases where the compiler can move an
object rather than copying it. A very simple case of where a move could
theoretically happen (though I'm not sure how much this happens in practice)
would be with code like

void foo()
{
    MyStruct m;
    bar(m);
}

void bar(MyStruct m)
{
    ...
}

bar takes its argument by value. So, it can't take a reference to avoid a
copy. However, because when bar is called, it's the last time that m is used
inside of foo, rather than copying m to where bar would use it as a
parameter, the compiler could choose to move the object to where bar expects
its parameter to be rather than copying it - so it could just blit the bits
over. In some cases, it may be able to just place m in the right place that
bar will get it without it being copied, but with more complicated code,
that's not possible. A far more likely case where objects would be moved is
with temporaries. e.g. with this code

func(foo(), bar(42), baz("hello"));

assuming that none of these functions return by ref, func is taking
temporaries from several functions, and the spec actually guarantees that
they will not be copied. So, if the compiler needs to move them to put them
in the right place for func to get them as parameters, it will.

In C++98, it really isn't legal for the compiler to move objects. This is
because it's legal to have classes/structs with pointers to any part of
themselves (e.g. if an object contained a static array, it could also
contain a pointer to one of the elements in that array), meaning that if
they were moved rather than copied, such pointers would then point to the
wrong place, putting the object in an invalid state. This means that C++98
does a lot of copying that shouldn't be necessary and is part of why const&
takes rvalues in C++. C++11 added move constructors to solve that problem.
So, now, in C++, if an object has a move constructor, and the compiler wants
to move it, it can call the move constructor, allowing for the object to do
stuff like fix its internal pointers so that they point to the correct place
after the object has been moved.

D's solution to this was to just say that having a struct with a pointer to
its own internals is not supported, and if you do it, your object will end
up in an invalid state whenever it's moved. The result is that the compiler
is allowed to move structs whenever it decides that it's a good idea. This
obviously isn't good for the rare cases where you really do want an object
with a pointer to its own internals, but it's fantastic for the typical
case, because it gets rid of the need for a lot of copying. So, unlike
C++98, objects can be moved freely, and unlike C++11, there's no need for
move constructors. This significantly reduced the need for something like
const& in D, and actually, one of the main motivators for the DIP to support
passing rvalues by ref is for interacting with C++ code that does it.

However, the guys at Weka (who are doing lots of low level stuff in their
product because of how high performance it is) came up with a use case where
they basically needed move constructors. So, one of the Weka employees wrote
a DIP for introducing what is essentially the move equivalent of a postblit
constructor. Unlike with C++, it wouldn't be necessary for an object to be
moveable, but it _would_ allow for structs to essentially have a move
constructor for those cases where the default behavior is not enough
(similar to how copy constructors and postblit constructors are not necesary
if the default copying behavior is enough but _are_ necessary when it
isn't). I don't think that the DIP has been implemented yet, but it _has_
been approved.

https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1014.md

If you really want to know more about moving, I'd suggest that you read up
on move constructors and move semantics. There should be plenty of resources
online that talk about the issue in C++, and I expect that a number of them
explain it better than I do.

The key thing here for D is that all structs are considered to be moveable
without the need for any kind of move constructor.

- Jonathan M Davis





More information about the Digitalmars-d-learn mailing list