www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Allocating an empty non null associative arary

reply Superstar64 <Hexagonalstar64 gmail.com> writes:
I want to be modify an associative array by reference from 
another function. However null associative arrays are pass by 
value. How do I generically create an empty associative array?
---
import std.stdio;


void addElement(int[int] data){
	data[0] = 0;
}

void nonempty() {
	int[int] items = [1 : 1];
	writeln(items); // [1 : 1]
	addElement(items);
	writeln(items); // [0 : 0, 1 : 1]
}

void empty(){
	int[int] items = null;
	writeln(items); // []
	addElement(items);
	writeln(items); // []
}

void main(){
	nonempty();
	empty();
}
---
Mar 30 2020
next sibling parent Mike Parker <aldacron gmail.com> writes:
On Tuesday, 31 March 2020 at 02:51:11 UTC, Superstar64 wrote:
 I want to be modify an associative array by reference from 
 another function. However null associative arrays are pass by 
 value. How do I generically create an empty associative array?
 ---
 import std.stdio;


 void addElement(int[int] data){
 	data[0] = 0;
 }
void addElement(ref int[int] data){ data[0] = 0; }
Mar 30 2020
prev sibling next sibling parent Vladimir Panteleev <thecybershadow.lists gmail.com> writes:
On Tuesday, 31 March 2020 at 02:51:11 UTC, Superstar64 wrote:
 How do I generically create an empty associative array?
If you can't pass it by ref, then adding and then removing an element is the only way I know. /// Ensure that arr is non-null if empty. V[K] nonNull(K, V)(V[K] aa) { if (aa !is null) return aa; aa[K.init] = V.init; aa.remove(K.init); assert(aa !is null); return aa; } unittest { int[int] aa; assert(aa is null); aa = aa.nonNull; assert(aa !is null); assert(aa.length == 0); }
Mar 30 2020
prev sibling parent reply WebFreak001 <d.forum webfreak.org> writes:
On Tuesday, 31 March 2020 at 02:51:11 UTC, Superstar64 wrote:
 I want to be modify an associative array by reference from 
 another function. However null associative arrays are pass by 
 value. How do I generically create an empty associative array?
 ---
 import std.stdio;


 void addElement(int[int] data){
 	data[0] = 0;
 }

 void nonempty() {
 	int[int] items = [1 : 1];
 	writeln(items); // [1 : 1]
 	addElement(items);
 	writeln(items); // [0 : 0, 1 : 1]
 }

 void empty(){
 	int[int] items = null;
 	writeln(items); // []
 	addElement(items);
 	writeln(items); // []
 }

 void main(){
 	nonempty();
 	empty();
 }
 ---
This doesn't only happen with null, you will notice that that function will eventually not add anything if you only add values. This is because by adding values you might reallocate the map at some other place in memory and because it's not ref, the caller won't have the map point to the new reference and still only have the old data. Setting an existing key shouldn't reallocate, though it's not exactly specified but it hints towards it: https://dlang.org/spec/hash-map.html#construction_assignment_entries So basically if you plan to add or remove items from your map, it could reallocate your memory, so you need to pass your argument by ref, so it updates the caller reference. // use when inserting or updating void addElement(scope ref int[int] data) { data[0] = 1; } // use only when updating existing keys (though you might also consider using ref anyway) void changeElement(scope int[int] data) { data[0] = 2; } // use for only reading int readElement(scope const int[int] data) { return data[0]; } The ref signals the caller that the passed in data will be modified. It's like passing pointers, but the value cannot be null. The scope signals the caller that you don't escape the reference outside the function, so you can be sure the memory won't be altered after the function has finished. This is especially enforced if you pass -dip1000. The const signals the caller that you won't modify the data. If you use const, you need to be very strict with it and use it in all your functions consistently, also adding const (or preferably inout) to member functions of structs and classes not modifying their data. (like getters) - You might otherwise need to do ugly casts to remove the const to hack around a function definition which might break the correctness of your program. But if you can do const for your parameters, definitely add it. The attributes are both for good documentation but also to enforce correct usage by the compiler.
Mar 30 2020
next sibling parent reply Superstar64 <Hexagonalstar64 gmail.com> writes:
On Tuesday, 31 March 2020 at 06:51:16 UTC, WebFreak001 wrote:
 This doesn't only happen with null, you will notice that that 
 function will eventually not add anything if you only add 
 values. This is because by adding values you might reallocate 
 the map at some other place in memory and because it's not ref, 
 the caller won't have the map point to the new reference and 
 still only have the old data.
Is the D spec wrong then? It says that associative arrays have reference semantics. https://dlang.org/spec/hash-map.html#construction_and_ref_semantic
Mar 31 2020
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 3/31/20 9:41 AM, Superstar64 wrote:
 On Tuesday, 31 March 2020 at 06:51:16 UTC, WebFreak001 wrote:
 This doesn't only happen with null, you will notice that that function 
 will eventually not add anything if you only add values. This is 
 because by adding values you might reallocate the map at some other 
 place in memory and because it's not ref, the caller won't have the 
 map point to the new reference and still only have the old data.
Is the D spec wrong then? It says that associative arrays have reference semantics. https://dlang.org/spec/hash-map.html#construction_and_ref_semantic
It does have reference semantics, but just like any reference type (pointer, class reference, etc), if it is initialized somewhere else, that initialization does not come back to the original unless you use ref semantics. Easy to see with a pointer: void foo(int *ptr) { ptr = new int(5); } void main() { int *p; foo(p); // does not affect p } -Steve
Mar 31 2020
prev sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 3/31/20 2:51 AM, WebFreak001 wrote:
 On Tuesday, 31 March 2020 at 02:51:11 UTC, Superstar64 wrote:
 I want to be modify an associative array by reference from another 
 function. However null associative arrays are pass by value. How do I 
 generically create an empty associative array?
 ---
 import std.stdio;


 void addElement(int[int] data){
     data[0] = 0;
 }

 void nonempty() {
     int[int] items = [1 : 1];
     writeln(items); // [1 : 1]
     addElement(items);
     writeln(items); // [0 : 0, 1 : 1]
 }

 void empty(){
     int[int] items = null;
     writeln(items); // []
     addElement(items);
     writeln(items); // []
 }

 void main(){
     nonempty();
     empty();
 }
 ---
This doesn't only happen with null, you will notice that that function will eventually not add anything if you only add values. This is because by adding values you might reallocate the map at some other place in memory and because it's not ref, the caller won't have the map point to the new reference and still only have the old data.
This is an incorrect assumption. Associative arrays are pImpls. Once the main bucket holder is allocated it will not change. The bucket array may change, but the elements themselves are each individual allocations. So when the bucket array is grown, it might move, but the elements themselves are stationary, and the bucket holder (the main implementation struct) is stationary. My recommendation to the OP is to follow Vladimir's advice. I do the same thing in my code. I have considered adding a UFCS method to AAs to initialize them with zero elements, but haven't got around to it. -Steve
Mar 31 2020