www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - C++ mutable in D

reply Michael Galuza <riddlermichael gmail.com> writes:
Is there any analogue of C++ `mutable` in D? As far as I 
understand, this is impossible due to transitive constness, but 
maybe some variation of `Rebindable` will be useful?

P.S. Of course I mean 'fair' mutable, not qualifier-away `cast()`.
Jul 30 2021
next sibling parent reply Tejas <notrealemail gmail.com> writes:
On Friday, 30 July 2021 at 19:17:55 UTC, Michael Galuza wrote:
 Is there any analogue of C++ `mutable` in D? As far as I 
 understand, this is impossible due to transitive constness, but 
 maybe some variation of `Rebindable` will be useful?

 P.S. Of course I mean 'fair' mutable, not qualifier-away 
 `cast()`.
Nope. No logical const, head const, or tail const in D; just pure transitive const. Also remember that casting away const is undefined behaviour in D (unlike being defined and supported in C++).
Jul 30 2021
next sibling parent reply IGotD- <nise nise.com> writes:
On Friday, 30 July 2021 at 20:09:19 UTC, Tejas wrote:
 Nope. No logical const, head const, or tail const in D; just 
 pure transitive const.

 Also remember that casting away const is undefined behaviour in 
 D (unlike being defined and supported in C++).
Thank you D for not having "mutable" as in C++. I don't see mutable often in C++ and is one of those corners in C++ that I don't think make much sense. const is const, easy. In C++ const might not be ROMable. In D immutable is ROMable and transistive const helps this (unless there is some escape hatch I don't know about).
Jul 30 2021
next sibling parent Tejas <notrealemail gmail.com> writes:
On Friday, 30 July 2021 at 21:53:01 UTC, IGotD- wrote:
 On Friday, 30 July 2021 at 20:09:19 UTC, Tejas wrote:
 Nope. No logical const, head const, or tail const in D; just 
 pure transitive const.

 Also remember that casting away const is undefined behaviour 
 in D (unlike being defined and supported in C++).
Thank you D for not having "mutable" as in C++. I don't see mutable often in C++ and is one of those corners in C++ that I don't think make much sense. const is const, easy. In C++ const might not be ROMable. In D immutable is ROMable and transistive const helps this (unless there is some escape hatch I don't know about).
Well, Jonathan Davies disagrees: http://jmdavisprog.com/articles/why-const-sucks.html
Jul 30 2021
prev sibling parent reply Gollan <gollan mail.com> writes:
On Friday, 30 July 2021 at 21:53:01 UTC, IGotD- wrote:
 On Friday, 30 July 2021 at 20:09:19 UTC, Tejas wrote:
 Thank you D for not having "mutable" as in C++. I don't see 
 mutable often in C++ and is one of those corners in C++ that I 
 don't think make much sense.
I'm really surprised you think it doesn't make much sense. What about: - Locking a mutex to avoid data races when returning a class member from a const getter function - Lazy resource allocation (caching) on first call of a const member function
Jul 31 2021
parent IGotD- <nise nise.com> writes:
On Saturday, 31 July 2021 at 12:27:28 UTC, Gollan wrote:
 I'm really surprised you think it doesn't make much sense. What 
 about:

 - Locking a mutex to avoid data races when returning a class 
 member from a const getter function
 - Lazy resource allocation (caching) on first call of a const 
 member function
They way I look at it if you don't want the code alter the *memory location* then it must be immutable/const. The way you describe it, it is based on what the API expect to do with the object even if there might be internal memory writes, like the semaphore example. With structures where members are expanded into the structure, then they share the memory location and partial const in my opinion should be prohibited. Now this can be different with classes as members are allocated independently but I'm sure about the practical benefit. It boils down to what we expect const to represent. I used the word ROMable for a reason meaning the allocated memory should not alter.
Jul 31 2021
prev sibling parent reply Jack Applegame <japplegame gmail.com> writes:
On Friday, 30 July 2021 at 20:09:19 UTC, Tejas wrote:
 Also remember that casting away const is undefined behaviour in 
 D (unlike being defined and supported in C++).
No. Casting const away IS NOT undefined behavior in both D and C++. Modifying a const object IS undefined behavior in both D and C++. There is not much difference between D and C++ in that way.
Jul 31 2021
next sibling parent Paulo Pinto <pjmlp progtools.org> writes:
On Sunday, 1 August 2021 at 06:15:30 UTC, Jack Applegame wrote:
 On Friday, 30 July 2021 at 20:09:19 UTC, Tejas wrote:
 Also remember that casting away const is undefined behaviour 
 in D (unlike being defined and supported in C++).
No. Casting const away IS NOT undefined behavior in both D and C++. Modifying a const object IS undefined behavior in both D and C++. There is not much difference between D and C++ in that way.
Not in C++, when the const object modification is taking place via member variables declared as mutable accessed from a const member function. This is also a thing in Swift and Rust, by the way, they just use other mechanisms to do the same.
Aug 01 2021
prev sibling parent Paul Backus <snarwin gmail.com> writes:
On Sunday, 1 August 2021 at 06:15:30 UTC, Jack Applegame wrote:
 On Friday, 30 July 2021 at 20:09:19 UTC, Tejas wrote:
 Also remember that casting away const is undefined behaviour 
 in D (unlike being defined and supported in C++).
No. Casting const away IS NOT undefined behavior in both D and C++. Modifying a const object IS undefined behavior in both D and C++. There is not much difference between D and C++ in that way.
In D, casting away const from a pointer and mutating the pointed-to object is UB even if the object it points to was not originally declared as `const` or `immutable`: ```d int n 123; // mutable object const(int)* pc = &n; int* pm = cast(int*) pc; *pm = 456; // undefined behavior ``` In C++, the above is allowed. Source: https://dlang.org/spec/cpp_interface.html#comparing-d-immutable-and-const-with-cpp-const
Aug 01 2021
prev sibling next sibling parent Dukc <ajieskola gmail.com> writes:
On Friday, 30 July 2021 at 19:17:55 UTC, Michael Galuza wrote:
 Is there any analogue of C++ `mutable` in D? As far as I 
 understand, this is impossible due to transitive constness, but 
 maybe some variation of `Rebindable` will be useful?

 P.S. Of course I mean 'fair' mutable, not qualifier-away 
 `cast()`.
Not any good analogue. There are some situation-dependant possibilities though. First one, you could do some template magic. Something like: ```d template constIf(bool cond, T) { static if(cond) alias constIf = const(T); else alias constIf = T; } struct AType(bool isConst) { constIf!(isConst, int[]) a; constIf!(isConst, int[]) b; int[] iPretendToBeMutable; } ``` Second possibility is that you store the mutable "element" in some external data structure, and store it's key or index in the `const` `struct`/`class`. This is what I'd probably do.
Aug 01 2021
prev sibling next sibling parent reply Michael Galuza <riddlermichael gmail.com> writes:
On Friday, 30 July 2021 at 19:17:55 UTC, Michael Galuza wrote:
 Is there any analogue of C++ `mutable` in D? As far as I 
 understand, this is impossible due to transitive constness, but 
 maybe some variation of `Rebindable` will be useful?

 P.S. Of course I mean 'fair' mutable, not qualifier-away 
 `cast()`.
Let's slightly reformulate my question. Can we write in D smth like this: ``` struct Mutable(T) { /* magic */ } struct Shape { private Mutable!double area; double getArea() const { if (area == 0) { area = computeArea(); } return area; } } ``` In other words, can we implement method `void S.opAssign(T value) const` of some struct `S` which change internal state of `S` and this method doesn't have UB and doesn't break D's type system.
Aug 01 2021
next sibling parent Paul Backus <snarwin gmail.com> writes:
On Sunday, 1 August 2021 at 18:21:32 UTC, Michael Galuza wrote:
 In other words, can we implement method `void S.opAssign(T 
 value) const` of some struct `S` which change internal state of 
 `S` and this method doesn't have UB and doesn't break D's type 
 system.
No. Mutating an object typed as `const` is always UB in D.
Aug 01 2021
prev sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 8/1/21 2:21 PM, Michael Galuza wrote:
 On Friday, 30 July 2021 at 19:17:55 UTC, Michael Galuza wrote:
 Is there any analogue of C++ `mutable` in D? As far as I understand, 
 this is impossible due to transitive constness, but maybe some 
 variation of `Rebindable` will be useful?

 P.S. Of course I mean 'fair' mutable, not qualifier-away `cast()`.
Let's slightly reformulate my question. Can we write in D smth like this: ``` struct Mutable(T) { /* magic */ } struct Shape {     private Mutable!double area;     double getArea() const {         if (area == 0) { area = computeArea(); }         return area;     } } ``` In other words, can we implement method `void S.opAssign(T value) const` of some struct `S` which change internal state of `S` and this method doesn't have UB and doesn't break D's type system.
Yes. You need to use global space to do it. e.g.: ```d struct Shape { private static double[size_t] areaLookup; private const size_t magicNumber; // set on constructor double getArea() const { return areaLookup.require(magicNumber, computeArea()); } } ``` I've argued in the past that since this is possible, putting the memory inside (or along-side) the instance isn't all that different, and there are surely other typesystem-allowable ways (I think there was a plan at some point to use affixAllocator to do something like this). But "possible" here is not exactly equivalent to "desirable". What you need is some way to clarify that the "mutable" data is not actually part of the instance, but some nebulous region that is carried along with it. -Steve
Aug 02 2021
prev sibling next sibling parent Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Friday, 30 July 2021 at 19:17:55 UTC, Michael Galuza wrote:
 Is there any analogue of C++ `mutable` in D? As far as I 
 understand, this is impossible due to transitive constness, but 
 maybe some variation of `Rebindable` will be useful?

 P.S. Of course I mean 'fair' mutable, not qualifier-away 
 `cast()`.
Not possible as other people mentioned. In this case the best approach would be to either design your struct to contain const and mutable fields, and design it's interface accordingly, or you could make a mutable decorator that would be mutable and expose mutable aspects, while keeping the decorated instance const. Best regards, Alexandru.
Aug 01 2021
prev sibling parent reply Dukc <ajieskola gmail.com> writes:
On Friday, 30 July 2021 at 19:17:55 UTC, Michael Galuza wrote:
 [snip]
We are saying that there is no direct analogue for `mutable` in D. It turns out we were all wrong - there is, and it even works in ` safe`! Behold: ```d safe: alias MutableDel(T) = safe ref T delegate(); struct Foo { int normalMember; private MutableDel!int _mutableMember; ref mutableMember() const {return _mutableMember();} this(int a, int b) { normalMember = a; _mutableMember = makeMutable(b); } } MutableDel!T makeMutable(T)(T mem) safe { auto varArr = [mem]; return ref () => varArr[0]; } void main() { import std; const foo = Foo(5, 10); foo.mutableMember.writeln; //10 foo.mutableMember = 15; foo.mutableMember.writeln; } ``` Now, I definitely don't recommend using this. It might well end up being considered as a bug, and thus stop working in the future. Also the optimizer might not take this possibility into account, thus injecting bugs to your code.
Aug 03 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Tuesday, 3 August 2021 at 13:02:38 UTC, Dukc wrote:
 Now, I definitely don't recommend using this. It might well end 
 up being considered as a bug, and thus stop working in the 
 future. Also the optimizer might not take this possibility into 
 account, thus injecting bugs to your code.
It's a known bug, since 2008: https://issues.dlang.org/show_bug.cgi?id=1983
Aug 03 2021
parent reply Dukc <ajieskola gmail.com> writes:
On Tuesday, 3 August 2021 at 14:01:20 UTC, Paul Backus wrote:
 On Tuesday, 3 August 2021 at 13:02:38 UTC, Dukc wrote:
 Now, I definitely don't recommend using this. It might well 
 end up being considered as a bug, and thus stop working in the 
 future. Also the optimizer might not take this possibility 
 into account, thus injecting bugs to your code.
It's a known bug, since 2008: https://issues.dlang.org/show_bug.cgi?id=1983
I don't think it's that one. I'm not abusing the delegates context pointer mutablity, I'm abusing the fact that a `const` or `immutable` `delegate` may have a mutable return type: ```d safe: void main() { import std; auto varArr = [5]; immutable del = () => varArr[0]; del().writeln; varArr[0] = 10; del().writeln; } ```
Aug 03 2021
parent Paul Backus <snarwin gmail.com> writes:
On Tuesday, 3 August 2021 at 15:33:34 UTC, Dukc wrote:
 On Tuesday, 3 August 2021 at 14:01:20 UTC, Paul Backus wrote:
 https://issues.dlang.org/show_bug.cgi?id=1983
I don't think it's that one. I'm not abusing the delegates context pointer mutablity, I'm abusing the fact that a `const` or `immutable` `delegate` may have a mutable return type: ```d safe: void main() { import std; auto varArr = [5]; immutable del = () => varArr[0]; del().writeln; varArr[0] = 10; del().writeln; } ```
This example is abusing the fact that an `immutable` delegate can have a mutable context pointer--i.e., that immutability of delegate contexts is not transitive. If you try to replace the delegate with a user-defined type... ```d struct Delegate { int* context; this(int* p) { context = p; } int opCall() { return *context; } } void main() { int* p = new int(5); immutable dg = Delegate(p); } ``` The compiler correctly points out that the conversion to `immutable` is invalid:
 Error: cannot implicitly convert expression 
 `Delegate(null).this(p)` of type `Delegate` to 
 `immutable(Delegate)`
You're right, though, that it's not exactly issue 1983. I think the bugzilla issue for this specific case is https://issues.dlang.org/show_bug.cgi?id=16058.
Aug 03 2021