View mode: basic / threaded / horizontal-split · Log in · Help
June 26, 2012
const ref in opAssign
I just finished reading the chapters on user defined types and, 
(as a C++ dev), this one line struc out to me as very odd:

"ref Widget opAssign(ref Widget rhs) {..}"

This means that during an assignment, I could potentially change 
"Other" (!). This seems like a blatant violation of the expected 
behavior of =. What's more, it prevents the assignment from a 
const object...

I am very tempted to change the call to "const ref". Is this 
un-advised?

Mr. Alexandrescu goes on to mention a "pass by value" in case you 
wanted to assign from a temporary, mentioning:
w = Widget(50); // Error!
// Cannot bind an rvalue of type Widget to ref Widget!

The only problem is that if I do this, all of my calls are then 
re-routed to pass by value, and none to the pass-by-const-ref.

----

Is there any way to enforce const correctness, while still 
keeping advantage of both calls?

PS: using Mr. Alexandrescu's design, it is not possible to assign 
from a const Widget, I get:
hello.d(50): Error: function hello.Widget.opAssign (Widget rhs) 
is not callable using argument types (const(Widget))
hello.d(50): Error: cannot implicitly convert expression (w2) of 
type const(Widget) to Widget

const can't be passed by value...?
June 26, 2012
Re: const ref in opAssign
On Tuesday, June 26, 2012 14:44:45 monarch_dodra wrote:
> I just finished reading the chapters on user defined types and,
> (as a C++ dev), this one line struc out to me as very odd:
> 
> "ref Widget opAssign(ref Widget rhs) {..}"
> 
> This means that during an assignment, I could potentially change
> "Other" (!). This seems like a blatant violation of the expected
> behavior of =. What's more, it prevents the assignment from a
> const object...
> 
> I am very tempted to change the call to "const ref". Is this
> un-advised?
> 
> Mr. Alexandrescu goes on to mention a "pass by value" in case you
> wanted to assign from a temporary, mentioning:
> w = Widget(50); // Error!
> // Cannot bind an rvalue of type Widget to ref Widget!
> 
> The only problem is that if I do this, all of my calls are then
> re-routed to pass by value, and none to the pass-by-const-ref.
> 
> ----
> 
> Is there any way to enforce const correctness, while still
> keeping advantage of both calls?
> 
> PS: using Mr. Alexandrescu's design, it is not possible to assign
> from a const Widget, I get:
> hello.d(50): Error: function hello.Widget.opAssign (Widget rhs)
> is not callable using argument types (const(Widget))
> hello.d(50): Error: cannot implicitly convert expression (w2) of
> type const(Widget) to Widget
> 
> const can't be passed by value...?

Depending on your type, taking const ref Widget would be a big problem, 
because you wouldn't be able to assign a const member variable's value of rhs 
to a non-const member variable of this (e.g. if it's a class) without making a 
deep copy. Or it could be just fine. It all depends on your type and what 
you're trying to do. Regardless, the solution is almost certainly to have 
multiple overloads. Each of the overloads does this

// Works with any Widget if Widget is a value type and any non-const Widget
// if it's a reference type.
ref Widget opAssign(Widget rhs) {}

// Works with any Widget as long as you can copy all of the member variables
// when they're const.
ref Widget opAssign(const Widget rhs) {}

// Works with non-const Widgets which are lvalues only.
ref Widget opAssign(ref Widget rhs) {}

// Works with any Widgets which are lvalues as long as you can copy all of the
// member variables when they're const.
ref Widget opAssign(ref Widget rhs) {}

In most cases, I would expect you to have these two overloads

ref Widget opAssign(const Widget rhs) {}
ref Widget opAssign(const ref Widget rhs) {}

It then works with const (as long as you're not dealing with a type which 
can't really be copied when it's const) and both rvalues and lvalues. The 
rvalue version must be const to ensure that constness does not affect which 
overload gets called (just l/rvalue-ness). This is particularly critical if 
the rvalue version simply calls the lvalue version, because if it's not const, 
you'd get infinite recursion.

- Jonathan M Davis
June 26, 2012
Re: const ref in opAssign
On Tuesday, 26 June 2012 at 15:40:48 UTC, Jonathan M Davis wrote:
> On Tuesday, June 26, 2012 14:44:45 monarch_dodra wrote:
>
> Depending on your type, taking const ref Widget would be a big 
> problem,
> because you wouldn't be able to assign a const member 
> variable's value of rhs
> to a non-const member variable of this (e.g. if it's a class) 
> without making a
> deep copy. Or it could be just fine. It all depends on your 
> type and what
> you're trying to do. Regardless, the solution is almost 
> certainly to have
> multiple overloads. Each of the overloads does this
>
> // Works with any Widget if Widget is a value type and any 
> non-const Widget
> // if it's a reference type.
> ref Widget opAssign(Widget rhs) {}
>
> // Works with any Widget as long as you can copy all of the 
> member variables
> // when they're const.
> ref Widget opAssign(const Widget rhs) {}
>
> // Works with non-const Widgets which are lvalues only.
> ref Widget opAssign(ref Widget rhs) {}
>
> // Works with any Widgets which are lvalues as long as you can 
> copy all of the
> // member variables when they're const.
> ref Widget opAssign(ref Widget rhs) {}
>
> In most cases, I would expect you to have these two overloads
>
> ref Widget opAssign(const Widget rhs) {}
> ref Widget opAssign(const ref Widget rhs) {}
>
> It then works with const (as long as you're not dealing with a 
> type which
> can't really be copied when it's const) and both rvalues and 
> lvalues. The
> rvalue version must be const to ensure that constness does not 
> affect which
> overload gets called (just l/rvalue-ness). This is particularly 
> critical if
> the rvalue version simply calls the lvalue version, because if 
> it's not const,
> you'd get infinite recursion.
>
> - Jonathan M Davis

Thanks for the in-depth explanation! It is still not very clear 
to me, mostly because I don't understand "you wouldn't be able to 
assign a const member variable's value of rhs to a non-const 
member variable of this". This works fine in C++. Must be because 
I've never used a language with reference semantics before.

Either that, or I'm miss-understanding the "ref" keyword. I 
thought "const ref" meant pass by const reference, but it would 
appear it means a reference to a const object... or something 
like that. I'll need to read the chapter on const too.

Either way, I guess I'll have to become more familiar with the 
language to fully appreciate this.
June 26, 2012
Re: const ref in opAssign
"monarch_dodra" , dans le message (digitalmars.D:170728), a écrit :
> Thanks for the in-depth explanation! It is still not very clear 
> to me, mostly because I don't understand "you wouldn't be able to 
> assign a const member variable's value of rhs to a non-const 
> member variable of this". This works fine in C++. Must be because 
> I've never used a language with reference semantics before.

In D, const is transitive. It mean that if an instance is const, 
everything that is refered by this instance is const. Thus, I can't copy 
a reference of a const instance to make a non-const reference.

Example:

struct A
{
 int* x;
 ref A opAssign(const ref other)
 {
   this.x = other.x; // error: other.x, which is a const(int*) because 
                     // of transitivity, can't be assign to this.x 
                     // (which is a non-const int*).
 }
}

You need to write either:
 ref A opAssign(ref other)
 {
   this.x = other.x; // no problem, other.x is not const.
   // Note: the value this.x is now shared between this and other
 }
or:
 ref A opAssign(const ref other) // deep copy
 {
   this.x = new int(*other.x);
   // this is a deep copy: a new int is created to take other.x's value
 }


If the structure contain no references at all, there is no problem, and 
opAssign should be const ref, with a const overload for l-values, as 
previously said.

-- 
Christophe
June 26, 2012
Re: const ref in opAssign
On Tuesday, June 26, 2012 18:32:37 monarch_dodra wrote:
> Thanks for the in-depth explanation! It is still not very clear
> to me, mostly because I don't understand "you wouldn't be able to
> assign a const member variable's value of rhs to a non-const
> member variable of this". This works fine in C++. Must be because
> I've never used a language with reference semantics before.
> 
> Either that, or I'm miss-understanding the "ref" keyword. I
> thought "const ref" meant pass by const reference, but it would
> appear it means a reference to a const object... or something
> like that. I'll need to read the chapter on const too.
> 
> Either way, I guess I'll have to become more familiar with the
> language to fully appreciate this.

Unlike in C++, a const ref (or const T& in C++) will not accept rvalues. The 
only difference between ref and const ref is that const ref takes const 
lvalues. There has been some discussion on changing this, but apparently there 
are some issues that arise from accepting rvalues for const& the way that C++ 
does which we were trying to avoid, so const ref does not accept rvalues.

As for "you wouldn't be able to assign a const member variable's value of rhs 
to a non-const member of this," the problem is that D's const is transitive. 
Once part of an object is const, _everything_ that it refers to is const, and 
casting away const and mutating the object is undefined behavior (unlike 
C/C++). This provides much stronger guarantees for const and is required to 
properly support immutable, but it _is_ more restrictive. This means that if 
you have a const object and you want to assign it to a non-const object, if 
it's not a value type, the _only_ way to do it without subverting the type 
system is to create a deep copy. So, if you have

struct S
{
T* value;
}

the opAssign for S would have to make a deep copy of value rather than simply 
assigning rhs' value to S' value. That's not entirely unlike C++ (especially 
if you're not casting away const in C++) - and you often want to make a deep 
copy anyway - but the additional strictness of D's const makes it so that 
there are more cases where you have to worry about and work around constness 
than you would in C++.

- Jonathan M Davis
June 26, 2012
Re: const ref in opAssign
On 06/26/2012 02:44 PM, monarch_dodra wrote:
>
> Is there any way to enforce const correctness, ...

Note that 'const correctness' is a C++ term that does not really have
an obvious counterpart in D.
June 26, 2012
Re: const ref in opAssign
On Tuesday, June 26, 2012 20:10:59 Timon Gehr wrote:
> On 06/26/2012 02:44 PM, monarch_dodra wrote:
> > Is there any way to enforce const correctness, ...
> 
> Note that 'const correctness' is a C++ term that does not really have
> an obvious counterpart in D.

Really? I'd have said that a type was const correct if every one of its 
functions that could be const was const, and that applies to D as much as C++. 
It's just that you don't have the same issues with types subverting const in D 
that you have in C++, since there is no mutable keyword and casting away const 
and mutating the object is undefined.

- Jonathan M Davis
June 26, 2012
Re: const ref in opAssign
On 06/26/2012 08:40 PM, Jonathan M Davis wrote:
> On Tuesday, June 26, 2012 20:10:59 Timon Gehr wrote:
>> On 06/26/2012 02:44 PM, monarch_dodra wrote:
>>> Is there any way to enforce const correctness, ...
>>
>> Note that 'const correctness' is a C++ term that does not really have
>> an obvious counterpart in D.
>
> Really? I'd have said that a type was const correct if every one of its
> functions that could be const was const, and that applies to D as much as C++.

In C++ _every_ method can be const -- it is not more than 
conventionalised interface documentation.

In D, there is no way to tell whether some virtual method can be const 
without taking into account the entire code base.

This makes this definition of const correctness moot immediately for 
both C++ and D.

> It's just that you don't have the same issues with types subverting const in D
> that you have in C++, since there is no mutable keyword and casting away const
> and mutating the object is undefined.
>
> - Jonathan M Davis

Exactly, there is no obvious counterpart.
June 26, 2012
Re: const ref in opAssign
On Tuesday, 26 June 2012 at 12:44:46 UTC, monarch_dodra wrote:
> I just finished reading the chapters on user defined types and, 
> (as a C++ dev), this one line struc out to me as very odd:
>
> "ref Widget opAssign(ref Widget rhs) {..}"
>
> This means that during an assignment, I could potentially 
> change "Other" (!). This seems like a blatant violation of the 
> expected behavior of =. What's more, it prevents the assignment 
> from a const object...
>
> I am very tempted to change the call to "const ref". Is this 
> un-advised?
>
> Mr. Alexandrescu goes on to mention a "pass by value" in case 
> you wanted to assign from a temporary, mentioning:
> w = Widget(50); // Error!
> // Cannot bind an rvalue of type Widget to ref Widget!
>
> The only problem is that if I do this, all of my calls are then 
> re-routed to pass by value, and none to the pass-by-const-ref.
>
> ----
>
> Is there any way to enforce const correctness, while still 
> keeping advantage of both calls?
>
> PS: using Mr. Alexandrescu's design, it is not possible to 
> assign from a const Widget, I get:
> hello.d(50): Error: function hello.Widget.opAssign (Widget rhs) 
> is not callable using argument types (const(Widget))
> hello.d(50): Error: cannot implicitly convert expression (w2) 
> of type const(Widget) to Widget
>
> const can't be passed by value...?

 I actually remember having this issue (I'll look up the 
reference to it later). The issue was the non-const non-ref 
version was a better fit; Now obviously it's wrong, but you can't 
convince the compiler of that. If the assign never modifies the 
incoming object, then why should you make two versions?

 Anyways. Try making 2 versions of opAssign with ref and try it 
again: ie a const-ref & non const-ref, and finally your pass by 
value.
Top | Discussion index | About this forum | D home