Extended Type Design.

Tyler Knott tywebmail at mailcity.com
Mon Mar 19 22:00:16 PDT 2007


I think I've got this.  Is the following right?

*final variables can be assigned only once in their lifetime, but are otherwise normal variables (i.e. they *do* have a 
memory address), except where constant folded by the compiler (this should be completely transparent to the programmer).

*const and invariant only make sense when applied to references, otherwise they are ignored or (preferably) errors.

*const references cannot be used to mutate the data they reference, but the data they reference may be mutated directly 
or through other non-const, non-invariant references.

*invariant references only reference data that will never be mutated (e.g. final variables).

*Both const and invariant references can be reassigned unless they are also final references.

*Conversely, when final is applied to the declaration of a reference, only that reference is protected from mutation, 
not the data it references, unless that reference is also const or invariant.

*Neither const nor invariant references can be cast to non-const or non-invariant references directly.

*The address of any variable or the value of any reference (of compatible type) can be assigned to const references.

*Only the addresses of final variables (I'm sure of this), the direct result of "new" statements (not sure on this one), 
and the values of other invariant references may be assigned to invariant references.

*const and invariant are viral (i.e. all references in the data referenced by a const or invariant reference is also 
const or invariant, respectively, when access through the const or invariant reference).

*final, when applied directly to POD structures (structs and array references), means no members of that structure can 
be modified after assignment, but is non-viral to references within the POD structure. (Not sure on this.)

*Function declarations cannot overloaded based on const or invariant. (Pretty sure on this.)
---------------------
Here are a whole bunch of contrived examples of most of these rules:

final int w = 5; 	//legal
final int x = 55;	//ditto
int y = 6; 		//legal
int z;			//ditto
const int i;		//either (preferably) illegal or ignored
invariant int i;	//ditto

x = 6; 			//illegal
y = 7; 			//legal

int* mut_pointer;
mut_pointer = &x; 	//illegal: this would allow x to be mutated
mut_pointer = &y; 	//obviously legal
*mut_pointer = 10;	//obviously legal

const int* const_pointer;
const_pointer = &x;		//legal
const_pointer = &y; 		//Also legal: const_pointer is mutable (non-final)
z = *const_pointer;		//Legal: you can read through const references
*const_pointer = 10;		//illegal: you can't mutate through const references

invariant int* inv_pointer = &x; 	//Legal: x is final and never changes
inv_pointer = &y 			//Illegal: y is non-final may change
inv_pointer = &w;			//Legal: inv_pointer is mutable (non-final)
y = *inv_pointer;			//Legal: data can be read through inv_pointer
*inv_pointer = y;			//Illegal: you can't mutate through invariant references

final int* final_pointer = &y;		//obviously legal
z = *final_pointer;			//obviously legal
*final_pointer = 2; 			//legal: y can be changed through final_pointer
final_pointer = &z; 			//illegal: final_pointer already assigned

final int* final_pointer2 = &x;		//illegal: final_pointer2 allows mutation

final const int* final_const_pointer = &x; //Legal: const pointer does not allow mutation
final_const_pointer = &w;		//Illegal: final_const_pointer is final and cannot be mutated
y = *final_const_pointer;		//Obviously legal
*final_const_pointer = y;		//Obviously illegal

final const int* final_const_pointer2 = &y; //Legal: neither y nor const care about underlying mutability

final invariant int* final_inv_pointer = &x; //Legal
final invariant int* final_inv_pointer2 = &y; //Illegal: y is mutable

struct S
{
	int a;
	char[] b;
	static S* opCall()
	{
		S* ret = new S;
		s.b = "test";
		return ret;
	}
}

final S final_s;	//final_s exists on the stack or in the global scope

final_s.a = 6;		//Illegal: s is final
final_s.b = "hello"; 	//ditto

final S final_s2 = *S();	//Legal
final_s2.b[2] = 'd';		//Legal: final is non-viral

final S* s_pointer = new S;	//Legal, *(s_pointer) exists on the heap
s_pointer.a = 6;		//Legal, *(s_pointer) can be mutated through s_pointer
s_pionter.b = "hello"; 		//ditto
s_pointer = new S;		//Illegal: s_pointer is already assigned

final S* s_pointer2 = S();	//Obviously legal

const S* const_s_pointer = new S;	//Legal
y = const_s_pointer.a;			//Legal
const_s_pointer.a = 5;			//Illegal
const_s_pointer = S();			//Legal: const_s_pionter can be reassigned

invariant S* inv_s_pointer = new S;	//Legal
y = inv_s_pionter.a;			//Legal
inv_s_pointer.a = 5;			//Illegal
inv_s_pointer = S();			//Not sure, but probably illegal: S* points to mutable data,
					//even though there are no other references to it.  How do
					//invariant pointers work with the static opCall() workaround for
					//missing struct constructors? Does NRVO deal with this?

class C
{
	int a;
	final S* s_in_c;
	this() { s_in_c = new S; } 	//Is it legal to assign final values in constructors?
					//What about in a static opCall() for structs?
	void increment_a() { this.a++; }
}

final C final_c = new C();
final_c = new C();		//Illegal: final_c already assigned.
y = final_c.a;			//Legal
final_c.a = x;			//Also legal
final_c.increment_a();		//Ditto

const C const_c = new C();
const_c = new C();		//Legal: reference const_c can be reassigned
const_c.a = y;			//Illegal: cannot mutate through const reference
const_c.increment_a();		//Legal??? Is the object's "this" reference non-const?

invariant C inv_c = new C();
inv_c = new C();		//legal: reference inv_c can be reassigned
ivn_c.a = y;			//Illegal: cannot mutate through invariant reference
inv_c.increment_a();		//Illegal???  The object shouldn't be able to be modified at all, but
				//how could the compiler catch this?



More information about the Digitalmars-d mailing list