Comment #0 by qs.il.paperinik — 2017-06-19T22:14:33Z
One cannot add values to an AA typed const(V)[K] because aa[key] is typed const(V). Add a method that has the semantics of this
V set(K, V)(ref const(V)[const(K)] aa, auto ref const(K) key, lazy const(V) value)
{
if (auto valPtr = key in aa) return *valPtr;
// cast away const as initialization is okay:
return (cast() aa[key]) = value();
}
but without double lookup and without unsafe cast.
The question arose here:
https://forum.dlang.org/post/[email protected]
Comment #1 by hsteoh — 2017-07-06T00:00:33Z
I'm not sure about this. Isn't the fact that you declared the AA's value type to be const a declaration that it cannot change once you initialize it?
If I have an array A of type const(int)[], I'd expect that I should not be able to assign new values to A[0], otherwise it breaks the const guarantee.
Similarly, if I have an AA of type const(int)[string], I'd expect that I cannot assign to AA["a"] once the AA is initialized, otherwise I'm violating the const guarantee.
Comment #2 by dlang-bugzilla — 2017-07-07T06:13:43Z
(In reply to hsteoh from comment #1)
> If I have an array A of type const(int)[], I'd expect that I should not be
> able to assign new values to A[0], otherwise it breaks the const guarantee.
If you have an empty array of const(int)[], you can't assign to its elements, but you can still append to it. In the same way, with an AA of type const(int)[string], there's no reason why you shouldn't be able to grow the AA.
Such a function would be good to have for performance reasons as well (as mentioned in passing by OP).
Comment #3 by hsteoh — 2017-07-07T16:16:03Z
@Vladimir: you have a good point, it should still be possible to append stuff to the AA.
What about a const overload to opIndexAssign that lets you assign to a new key, but aborts if the key already exists? I'm a little hesitant about adding a whole new method to set a key in an AA. I'm not sure what to do about the abort... throw an AssertError? RangeError?
Comment #4 by dlang-bugzilla — 2017-07-07T18:05:01Z
(In reply to hsteoh from comment #3)
> What about a const overload to opIndexAssign that lets you assign to a new
> key, but aborts if the key already exists?
I'm not sure; I think the semantics would be a bit surprising. E.g. generally if is(typeof(aa[key]=value)) is true, you'd expect it to work at runtime as well (for POD types, at least).
> I'm a little hesitant about
> adding a whole new method to set a key in an AA.
I think it would be useful, but I agree it's a bigger addition that might use more discussion. Not enough for a DIP, but it could be discussed in the pull request if one were to make one.
I know Martin wants to give a templated AA implementation another go soon:
https://wiki.dlang.org/Vision/2017H2_draft
Comment #5 by dlang-bugzilla — 2017-07-07T18:13:26Z
BTW, the performance use case of the proposed set method (which I called getOrAdd in my hashmap implementation):
struct S { int i, j, k, l, m; /* etc. */ }
S[int] aa;
// The goal is to increment aa[x].k
// If aa[x] doesn't exist, initialize it with S.init
// Currently, you have to do this:
if (auto p = x in aa)
(*p).k++;
else
{
S s;
s.k = 1;
aa[x] = s; // Wasteful - double lookup
}
Comment #6 by qs.il.paperinik — 2017-07-09T14:07:11Z
@Vladimir Exactly. It's a performance and possibility issue. We can discuss the name after everything else is done.
I propose a change to the signature + semantics:
V* set(K, V)(ref const(V)[const(K)] aa, auto ref const(K) key, lazy const(V) value)
{
if (auto valPtr = key in aa) return null;
// cast away const as initialization is okay:
(cast() aa[key]) = value();
return key in aa;
}
So it returns null if it was not set and a pointer to the object in the AA to be able to modify it after setting without additional lookups. I like addOrGet very much. Both methods have their range of applicability. With this one, you can do
if (auto valPtr = aa.set(key, val()))
{
valPtr.property = something();
manage(*valPtr);
}
The main difference between the two is what you intend to do:
- Yours assures the key has a value afterwards. It doesn't really care if the key is there already. One cannot even know using it.
- Mine expects the key not to be there beforehand. It is specifically designed to add this key-value pair.