Thread overview
Mimicking C++'s indexing behavior in D associative arrays
Feb 18, 2015
Matt Kline
Feb 18, 2015
Matt Kline
Feb 18, 2015
Rikki Cattermole
Feb 18, 2015
bearophile
Feb 18, 2015
FG
February 18, 2015
In C++, the index operator for maps will either return a reference to the existing value if the key can be found, or a reference to a new, default-initialized value if one with the given key cannot be found.

In D, an exception is thrown instead when a value with the given key cannot be found, similar to unordered_map::at in C++. So if I want to mimic the same behavior (get or initialize to default), I have to do something like

// Assume bar is some associative array of type Foo[string]
Foo* value = key in bar;
if (value) {
    bar[key] = Foo.init;
    value = &bar[key];
}
This seems sub-optimal, given that in involves three hashes (two lookups and one insertion). Is there a more efficient or cleaner way to do so?
February 18, 2015
On Wednesday, 18 February 2015 at 00:21:11 UTC, Matt Kline wrote:

> if (value) {

should of course be

 if (!value) {

Sorry for the typo.
February 18, 2015
On 18/02/2015 1:21 p.m., Matt Kline wrote:
> In C++, the index operator for maps will either return a reference to
> the existing value if the key can be found, or a reference to a new,
> default-initialized value if one with the given key cannot be found.
>
> In D, an exception is thrown instead when a value with the given key
> cannot be found, similar to unordered_map::at in C++. So if I want to
> mimic the same behavior (get or initialize to default), I have to do
> something like
>
> // Assume bar is some associative array of type Foo[string]
> Foo* value = key in bar;
> if (value) {
>      bar[key] = Foo.init;
>      value = &bar[key];
> }
> This seems sub-optimal, given that in involves three hashes (two lookups
> and one insertion). Is there a more efficient or cleaner way to do so?

So basically:

Foo v = bar.get(key, Foo.init)
bar[key] = v;

Get is like an ordinary index but it will return the given value if it does not exist in the AA.

Of course you probably want to create a new UFCS function to wrap your check + default initialize if it doesn't exist.

T grab(T, U)(T[U] aa, U key) if (is(T == struct)) {
    if (key !in aa)
        aa[key] = new T;
    return aa[key];
}

So:
Foo*[string] bar;
Foo v = *bar.grab("mykey");
February 18, 2015
Rikki Cattermole:

> Foo*[string] bar;
> Foo v = *bar.grab("mykey");

Is this the setdefault of Python dicts? If this need is strong a new function could be added to Phobos (or even druntime if you want to reduce the number of hash computations).

Bye,
bearophile
February 18, 2015
>> // Assume bar is some associative array of type Foo[string]
>> Foo* value = key in bar;
>> if (!value) {
>>      bar[key] = Foo.init;
>>      value = &bar[key];
>> }
>> This seems sub-optimal, given that in involves three hashes (two lookups
>> and one insertion). Is there a more efficient or cleaner way to do so?
>
> So basically:
>
> Foo v = bar.get(key, Foo.init)
> bar[key] = v;
>
> Get is like an ordinary index but it will return the given value if it does not exist in the AA.
>
> Of course you probably want to create a new UFCS function to wrap your check + default initialize if it doesn't exist.
>
> T grab(T, U)(T[U] aa, U key) if (is(T == struct)) {
>      if (key !in aa)
>          aa[key] = new T;
>      return aa[key];
> }

You are searching for the key twice and the original example used pointers.


There is a function called _aaGetX in the runtime that has exactly the required behaviour:

    // Get pointer to value in associative array indexed by key.
    // Add entry for key if it is not already there.
    void* _aaGetX(AA* aa, const TypeInfo keyti, in size_t valuesize, in void* pkey)

however, using it in normal code could be considered a hack, because it belongs to the internal implementation of associative arrays. Anyway, while waiting for a better solution to present itself, we might as well have a look at this very dirty one. ;)


    extern(C) void* _aaGetX(void* aa, const TypeInfo keyti, in size_t valuesize, in void* pkey);
            V* aaGet(K, V)(V[K] arr, K key) {
        return cast(V*)_aaGetX(cast(void*)&arr, typeid(K), V.sizeof, cast(void*)&key);
    }

    unittest {
        int[int] arr = [1: 10, 2: 20, 3: 30];
                int *val = arr.aaGet(3); // an existing value
        assert(*val == 30);
                val = arr.aaGet(4); // aa[4] will be created
        assert(*val == int.init);
        assert(arr[4] == int.init);
    }