www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Workarounds for Lack of Mutable Keyword

reply "Craig Black" <craigblack2 cox.net> writes:
Part of the uneasiness about D's const is that it is much more strict than 
C++'s, and so has the perception of being more difficult to work with.  For 
example, D lacks C++ mutable keyword, so that there is no way to specify a 
mutable field.  Janice suggested a solution to this that would probably work 
for most purposes:

There are trivial workarounds. Instead of

    class C
    {
        int x;
        mutable int y;
    }

    void f(const(C) c);

just do this:

    class C
    {
        int x;
    }

    class D
    {
        int y;
    }

    void f(const(C) c, D d);

Although this may be good for most, this doesn't work for me, because to use this approach, I would have to make an extra heap allocation, and use an extra parameter. An extra parameter may not seem like a big deal to some. However, a compiler can optimize small recursive functions with small numbers of parameters using registers, so there could be a significant performance penalty for adding a single parameter to a recursive function. I have a more hackish solution that won't cost me any performance. I'm pretty sure it will work, but I haven't quite fleshed out the details. Maybe some of you D template gurus could help me with this one. My idea is to have a ConstAssign template that would subvert const somehow and assign a value to const data. Here's how it would work to replace a mutable field: class C { int x; int y; void setY(int ny) { ConstAssign(y, ny); } } Since D has pointers and unions, there's probably some ugly hackish trick we can employ to subvert const. The idea is to put this ugly hack in a template function. This would provide a not-so-messy workaround fo the lack of mutable that doesn't cost anyperformance or memory. -Craig
Apr 02 2008
next sibling parent reply "Craig Black" <craigblack2 cox.net> writes:
I did it and it was very easy.  I didn't know you could cast away const so 
easily in D.  This works for const, but not invariant.  Since we have the 
ability to subvert const so easily, there should be no problem living 
without mutable fields.

import std.stdio;

void ConstAssign(T)(ref const T x, T y) { *cast(T*)&x = y; }

class A
{
public:
  int x;
  int y;
  void setY(int ny) const { ConstAssign(y, ny); }
}

void foo(const A a) { a.setY(2); }

int main(char[][] args)
{
  A a = new A;
  a.y = 1;
  foo(a);
  writefln(a.y);
  return 0;
}
Apr 02 2008
next sibling parent reply Robert Fraser <fraserofthenight gmail.com> writes:
Craig Black wrote:
 I did it and it was very easy.  I didn't know you could cast away const 
 so easily in D.  This works for const, but not invariant.  Since we have 
 the ability to subvert const so easily, there should be no problem 
 living without mutable fields.
 
 import std.stdio;
 
 void ConstAssign(T)(ref const T x, T y) { *cast(T*)&x = y; }
 
 class A
 {
 public:
  int x;
  int y;
  void setY(int ny) const { ConstAssign(y, ny); }
 }
 
 void foo(const A a) { a.setY(2); }
 
 int main(char[][] args)
 {
  A a = new A;
  a.y = 1;
  foo(a);
  writefln(a.y);
  return 0;
 }

ight, but be sure to synchronize for multi threaded access.
Apr 02 2008
parent "Craig Black" <craigblack2 cox.net> writes:
"Robert Fraser" <fraserofthenight gmail.com> wrote in message 
news:ft1r7d$1q0b$1 digitalmars.com...
 Craig Black wrote:
 I did it and it was very easy.  I didn't know you could cast away const 
 so easily in D.  This works for const, but not invariant.  Since we have 
 the ability to subvert const so easily, there should be no problem living 
 without mutable fields.

 import std.stdio;

 void ConstAssign(T)(ref const T x, T y) { *cast(T*)&x = y; }

 class A
 {
 public:
  int x;
  int y;
  void setY(int ny) const { ConstAssign(y, ny); }
 }

 void foo(const A a) { a.setY(2); }

 int main(char[][] args)
 {
  A a = new A;
  a.y = 1;
  foo(a);
  writefln(a.y);
  return 0;
 }

ight, but be sure to synchronize for multi threaded access.

In the case that I'm thinking of using this for, I know multiple threads won't be contending for the "mutable" fields. -Craig
Apr 02 2008
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 03/04/2008, Janice Caron <caron800 googlemail.com> wrote:
 One problem with this function is demonstrated by

     string s = "hello world";

     ConstAssign(h[0],'j');

Yikes! I think I must be going mad. That's too many typos for one day! Need more coffee. The h should have been an s. string s = "hello world"; ConstAssign(s[0],'j');
  Oops - it assigns invariants too! Even if you can be sure there are no
  other threads vying for access to s, still, those chars might be in a
  hardware-locked ROM segment.

  There's a reason why casting away const is not defined.

Apr 03 2008
prev sibling next sibling parent reply "Janice Caron" <caron800 googlemail.com> writes:
On 03/04/2008, Craig Black <craigblack2 cox.net> wrote:
  void ConstAssign(T)(ref const T x, T y) { *cast(T*)&x = y; }

One problem with this function is demonstrated by string s = "hello world"; ConstAssign(h[0],'j'); Oops - it assigns invariants too! Even if you can be sure there are no other threads vying for access to s, still, those chars might be in a hardware-locked ROM segment. There's a reason why casting away const is not defined.
Apr 03 2008
parent "Craig Black" <craigblack2 cox.net> writes:
"Janice Caron" <caron800 googlemail.com> wrote in message 
news:mailman.301.1207206839.2351.digitalmars-d puremagic.com...
 On 03/04/2008, Craig Black <craigblack2 cox.net> wrote:
  void ConstAssign(T)(ref const T x, T y) { *cast(T*)&x = y; }

One problem with this function is demonstrated by string s = "hello world"; ConstAssign(h[0],'j'); Oops - it assigns invariants too! Even if you can be sure there are no other threads vying for access to s, still, those chars might be in a hardware-locked ROM segment. There's a reason why casting away const is not defined.

If that compiles, I think it may be a bug. Invariant types shouldn't be implicitly convertible to const. -Craig
Apr 03 2008
prev sibling next sibling parent reply "Janice Caron" <caron800 googlemail.com> writes:
On 03/04/2008, Craig Black <craigblack2 cox.net> wrote:
  If that compiles, I think it may be a bug.  Invariant types shouldn't be
 implicitly convertible to const.

Yes they should. const means "I promise not to modify this". There is absolutely no problem with promising not to modify something which is invariant.
Apr 03 2008
parent "Craig Black" <craigblack2 cox.net> writes:
"Janice Caron" <caron800 googlemail.com> wrote in message 
news:mailman.304.1207217058.2351.digitalmars-d puremagic.com...
 On 03/04/2008, Craig Black <craigblack2 cox.net> wrote:
  If that compiles, I think it may be a bug.  Invariant types shouldn't be
 implicitly convertible to const.

Yes they should. const means "I promise not to modify this". There is absolutely no problem with promising not to modify something which is invariant.

Hmmm. Maybe you are right. I was just thinking that since invariant is a stronger guarantee than const, it shouldn't be implicitly convertible. But I suppose I agree with you after giving it a little more though.
Apr 03 2008
prev sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 03/04/2008, Janice Caron <caron800 googlemail.com> wrote:
  const means "I promise not to modify this". There is absolutely no
  problem with promising not to modify something which is invariant.

There is, of course, a /huge/ problem with promising not to modify something while keeping your fingers crossed behind your back. Promising not to modify something (which is what accepting a const parameter means), and then modifying it anyway, is called lying.
Apr 03 2008
prev sibling next sibling parent "Craig Black" <craigblack2 cox.net> writes:
The previous solution that I proposed only works because of a bug in DMD. 
See the "const/invariant bug" post for more info on the bug.  But I found 
another solution.  Const doesn't seem to cooperate well with templates, so

void ConstAssign(T)(ref const T a, T b) { *cast(T*)cast(int)(&a) = b; }

doesn't work but

void ConstAssign(T, S)(ref T a, S b) { *cast(S*)cast(int)(&a) = b; }

does work, as long as the second parameter not const.

Here's the full example:

import std.stdio;

void ConstAssign(T, S)(ref T a, S b) { *cast(S*)cast(int)(&a) = b; }

class A
{
public:
  int x = 0;
  const void setX(int nx) { ConstAssign(x, nx); }
}

void foo(const A a) { a.setX(1); }

int main(char[][] args)
{
  A a = new A;
  foo(a);
  writefln(a.x);
  return 0;
} 
Apr 03 2008
prev sibling parent "Craig Black" <craigblack2 cox.net> writes:
The second solution that I posted didn't work if the second parameter was 
const.  This solution will.  It uses a ForceCast template.  BTW, this 
template will work for more than just const.  Since D doesn't allow 
returning a reference, we must pass a pointer to ForceCast and dereference 
the result.

import std.stdio;

T ForceCast(T, S)(S a) { return *cast(T*)cast(int)(&a); }

class A
{
public:
  int x = 0;
  const void setX(const int nx) { *ForceCast!(int*)(&x) = nx; }
}

void foo(const A a) { a.setX(1); }

int main(char[][] args)
{
  A a = new A;
  foo(a);
  writefln(a.x);
  return 0;
} 
Apr 03 2008