Created
November 30, 2010 02:54
-
-
Save JesseKPhillips/721066 to your computer and use it in GitHub Desktop.
Creating Safe Mutable members in a const/immutable object.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This code comes from a D newsgroup discussion by Max Samukha | |
* and Simen kjaeraas: http://thread.gmane.org/gmane.comp.lang.d.general/43408/focus=43476 | |
* | |
* The intent is to provide a safe way to mutate some data in a const method. | |
* | |
* The rules to achieve this are: | |
* * Only mutable data can be assigned to a Mutable | |
* * Referenced fields can be modified (inner class fields, pointer target) | |
* * Value types can be modified if the encapsulating class | |
* is not declared const/immutable | |
*/ | |
module lconst; | |
import std.string; | |
import std.traits; | |
@trusted: | |
/** | |
* Mutable encapsulates an unqualified type allowing its referenced item to | |
* be safely modified within a const function or immutable class. | |
* | |
* Mutable may not be safe when passing immutable class to other threads | |
* or across the network. | |
* | |
* The other question is if a mutable inner class would ever be allocated in | |
* read-only memory when instantiating an immutable outer class. | |
*/ | |
struct Mutable( T ) if ( is( T : Unqual!T ) ) { | |
private T _payload; | |
this( T t ) { | |
_payload = t; | |
} | |
@trusted @property ref T get( )( ) const { | |
T* p = cast( T* )&_payload; | |
return *p; | |
} | |
alias get this; | |
@trusted ref opAssign( U : T )( auto ref U u ) { | |
T* p = cast( T* )&_payload; | |
return *p = u; | |
} | |
bool opEquals( )( ref const Mutable other ) const { | |
return _payload == other._payload; | |
} | |
bool opEquals( U )( auto ref U other ) const if ( is( U : T ) || is( T | |
: U ) ) { | |
return _payload == other; | |
} | |
string toString() { | |
return format("%s", _payload); | |
} | |
} | |
unittest { | |
struct Matrix | |
{ | |
double determinant() const | |
{ | |
if ( *m_dirty ) | |
{ | |
*m_determinant = 646.363; /* expensive calculation */; | |
*m_dirty = false; | |
} | |
return *m_determinant; | |
} | |
void set(int i, int j, double x) { *m_dirty = true; } | |
Mutable!(bool*) m_dirty; | |
Mutable!(double*) m_determinant; | |
}; | |
} | |
unittest { | |
class Window | |
{ | |
@safe void drawRectangle(int x, int y, int width, int height) | |
{ | |
} | |
} | |
class C | |
{ | |
int x, y, width, height; | |
Mutable!Window location; | |
this() | |
{ | |
location = new Window; | |
} | |
@safe void draw() const { location.drawRectangle(x, y, width, height); } | |
} | |
const C c = new C; | |
c.draw(); | |
} | |
unittest { | |
struct S { | |
int n; | |
} | |
struct foo { | |
int n; | |
foo* p; | |
Mutable!(S*) s; | |
Mutable!(int*) m; | |
void bar( ) const { | |
static assert(__traits(compiles, *m = *m + 1)); // works, m is newlevel | |
static assert(!__traits(compiles, n = n + 1)); // compile-time error, n is const for newlevel this | |
static assert(__traits(compiles, s.n = 3)); // Works, s.n is newlevel | |
static assert(!__traits(compiles, p.n = 4)); // compile-time error, p is newlevel, p.n is const for | |
// newlevel this | |
static assert(__traits(compiles, *p.m = *p.m + 1)); // Works, p.m is newlevel | |
} | |
} | |
void qux( const foo f ) {} | |
void quux( ref foo f ) { f.n++; } | |
const foo f; | |
f.bar( ); // compile-time error, f is const, cannot call newlevel | |
// functions on it | |
qux( f ); // compile-time error, const(foo) cannot be implicitly | |
// cast to newlevel(foo) | |
//quux( f ); // compile-time error, newlevel(foo) cannot be implicitly | |
// cast to foo | |
} | |
unittest { | |
@safe void bar( int n ) {} | |
class Inner { int n; } | |
class A { | |
Mutable!(int*) n; | |
Mutable!(int) n2; | |
Mutable!(Inner) innerClass; | |
// Would innerClass be placed into read-only memory | |
// when instantiated with: new immutable(A)? | |
this() { n = new int; innerClass = new Inner; } | |
void foo( int num ) { | |
n2 = num; | |
} | |
@safe void bar( int num ) const { | |
innerClass.n = num; | |
} | |
} | |
auto aImmu = new immutable(A); | |
auto aMu = new A; | |
int i = 8; | |
auto immuInner = new immutable(Inner); | |
static assert(!__traits(compiles, *aMu.n = &i), | |
"Assign pointer to int"); | |
static assert(!__traits(compiles, aMu.n = i), | |
"Assign int to pointer"); | |
static assert(__traits(compiles, *aImmu.n = i), | |
"Assign value to immutable object pointer"); | |
static assert(__traits(compiles, aImmu.bar(6)), | |
"Call function modifying Mutable Class"); | |
static assert(!__traits(compiles, aImmu.n = &i), | |
"Assign pointer to immutable object"); | |
static assert(!__traits(compiles, aImmu.n2 = i), | |
"Assign value to immutable object"); | |
static assert(!__traits(compiles, aImmu.foo(6)), | |
"Call function modifying Mutable value"); | |
static assert(!__traits(compiles, aImmu.innerClass = new Inner()), | |
"Assign class to Mutable value"); | |
static assert(__traits(compiles, *aMu.n = i), | |
"Assign value to mutable object pointer"); | |
static assert(__traits(compiles, aMu.n = &i), | |
"Assign pointer to mutable object"); | |
static assert(__traits(compiles, aMu.n2 = i), | |
"Assign value to mutable object"); | |
static assert(__traits(compiles, aMu.bar(6)), | |
"Call function modifying Mutable Class"); | |
static assert(__traits(compiles, aMu.foo(6)), | |
"Call function modifying Mutable value"); | |
static assert(__traits(compiles, aMu.innerClass = new Inner()), | |
"Assign class to Mutable value"); | |
static assert(!__traits(compiles, aMu.innerClass = immuInner), | |
"Assign immutable class to Mutable value"); | |
//FIXME: Fails, is this do to an alias this bug? | |
static assert(__traits(compiles, bar(a.n)), | |
"Call function that takes an int"); | |
// Other Items that should work when alias this is fixed | |
static assert(__traits(compiles, aMu.n2++), | |
"Increment int"); | |
static assert(__traits(compiles, aMu.n2 += 7), | |
"opOpAssign"); | |
aImmu.bar(6); | |
assert(aImmu.innerClass.n == 6); | |
} | |
unittest { | |
auto badClass = q{ class A { | |
Mutable!(int) n; | |
void bar( int num ) const { | |
n2 = num; | |
} | |
} }; | |
static assert(!__traits(compiles, mixin(badClass)), | |
"Const function modify Mutable value"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
One limitation is that value types declared as Mutable (Mutable!(int)) can not be changed if the encapsulating class is declared immutable. For this reason a Mutable value can not be changed inside a const function. I think this is acceptable.