Thread overview
Implicit dereferencing
Apr 02, 2013
Luís Marques
Apr 02, 2013
Luís Marques
Apr 02, 2013
Luís Marques
Apr 02, 2013
Luís Marques
Apr 02, 2013
kenji hara
Apr 02, 2013
Ali Çehreli
April 02, 2013
Consider this (non-portable) code:

{
    int[2] a;
    int[2] b;
    int[2] *c;
    c = &a;

    c[0] = 7;
    assert(a[0] == 7); // OK, as expected

    c[1] = 42;
    assert(b[0] == 42); // do we really want this semantics?
}

Because indexing c automatically dereferences the pointer, "c[0] = 7" assigns to a[0], as expected. This is the same as "(*c)[0] = 7", which is useful.

But "c[1] = 42" is equivalent to "(*(c+1)) = 42", which might not be obvious. In this case it ends writing to b[0]. Why not make the semantics be ((*c)+1) = 42, instead? Wouldn't that be more useful?

Also, why don't we have pointer dereferencing for AAs?

{
    struct Foo { int f; }

    // given that this works...
    Foo x;
    Foo *y = &x;
    x.f = 1;
    y.f = 2; // implicit dereferencing

    // coudln't this be made to work?...
    int[string] c;
    int[string] *d;
    d = &c;
    c["foo"] = 1;
    d["foo"] = 2; // fails, dereferencing must be explicit
    (*d)["foo"] = 2; // works, of course
}
April 02, 2013
On Mon, 01 Apr 2013 20:07:30 -0400, Luís Marques <luismarques@gmail.com> wrote:

> Consider this (non-portable) code:
>
> {
>      int[2] a;
>      int[2] b;
>      int[2] *c;
>      c = &a;
>
>      c[0] = 7;
>      assert(a[0] == 7); // OK, as expected
>
>      c[1] = 42;
>      assert(b[0] == 42); // do we really want this semantics?
> }
>
> Because indexing c automatically dereferences the pointer, "c[0] = 7" assigns to a[0], as expected. This is the same as "(*c)[0] = 7", which is useful.

This is not what is happening.  If you add:

writeln(a);

you will see:
7, 7

You see, indexing does NOT dereference the pointer, it's an index for that pointer.  c[0] means *(c + 0).  A pointer is essentially an unchecked slice, with undefined length.  This is how it works in C also.

c[1] is the same as *(c + 1), completely consistent (and also sets b to 42, 42)

-Steve
April 02, 2013
On Tuesday, 2 April 2013 at 02:52:48 UTC, Steven Schveighoffer wrote:
> You see, indexing does NOT dereference the pointer, it's an index for that pointer.  c[0] means *(c + 0).  A pointer is essentially an unchecked slice, with undefined length.  This is how it works in C also.
>
> c[1] is the same as *(c + 1), completely consistent (and also sets b to 42, 42)

OK, I think I see where I went astray. I was a case of bad induction from a few tests :-)
So, I guess what is happening is the following, right?

    int[2] a;
    int[2] *c;
    c = &a;

    c[0] = 7;  // same thing as below
    a = 7;      // same thing above

    (cast(int*) c)[0] = 7; // but different from this

I verified that c is a pointer to a.ptr, I guess what I didn't consider is that because c points to int[2], the assignment becomes the same as a = 7, and not a[0] = 7.

Still, what do you think of the struct vs AA automatic pointer dereferencing?
April 02, 2013
On Mon, 01 Apr 2013 23:23:49 -0400, Luís Marques <luismarques@gmail.com> wrote:

> On Tuesday, 2 April 2013 at 02:52:48 UTC, Steven Schveighoffer wrote:
>> You see, indexing does NOT dereference the pointer, it's an index for that pointer.  c[0] means *(c + 0).  A pointer is essentially an unchecked slice, with undefined length.  This is how it works in C also.
>>
>> c[1] is the same as *(c + 1), completely consistent (and also sets b to 42, 42)
>
> OK, I think I see where I went astray. I was a case of bad induction from a few tests :-)
> So, I guess what is happening is the following, right?
>
>      int[2] a;
>      int[2] *c;
>      c = &a;
>
>      c[0] = 7;  // same thing as below
>      a = 7;      // same thing above
>
>      (cast(int*) c)[0] = 7; // but different from this
>
> I verified that c is a pointer to a.ptr, I guess what I didn't consider is that because c points to int[2], the assignment becomes the same as a = 7, and not a[0] = 7.
>
> Still, what do you think of the struct vs AA automatic pointer dereferencing?

a pointer defines indexing.  To have implicit dereferencing for indexing on any pointer type would not be good.

For example:

string[int][2] aas;
string[int] *aaptr = aas.ptr;

auto x = aaptr[1];

If x is not a pointer to aas[1], then you would have to access it using *(aaptr + 1), which would suck.  Plus it would make pointers to indexed types inconsistent with all pointers to types that don't define indexing.

-Steve
April 02, 2013
On Tuesday, 2 April 2013 at 03:36:04 UTC, Steven Schveighoffer wrote:
> Plus it would make pointers to indexed types inconsistent with all pointers to types that don't define indexing.

Yeah, of course you're right...

Taking a step back, I think the problem here is that I wanted to have multiple references to the contents of an associative array. With a regular array, while you can't *store* a reference to the array "header" (ptr, length) without using pointers, you can have another array pointing to the same content. I guess you can't do something like that with an associative array, right? (mm... that kinda breaks the ideia of an AA as a more general array)
April 02, 2013
On Tue, 02 Apr 2013 00:12:37 -0400, Luís Marques <luismarques@gmail.com> wrote:

> On Tuesday, 2 April 2013 at 03:36:04 UTC, Steven Schveighoffer wrote:
>> Plus it would make pointers to indexed types inconsistent with all pointers to types that don't define indexing.
>
> Yeah, of course you're right...
>
> Taking a step back, I think the problem here is that I wanted to have multiple references to the contents of an associative array. With a regular array, while you can't *store* a reference to the array "header" (ptr, length) without using pointers, you can have another array pointing to the same content. I guess you can't do something like that with an associative array, right? (mm... that kinda breaks the ideia of an AA as a more general array)

An AA is a pImpl, meaning a pointer to implementation.  A simple copy is an alias.

HOWEVER, there is one horrible caveat.  You must have assigned an element at least once in order to alias:

int[int] aa1; // pImpl initialized to null
int[int] aa2; // pImpl initialized to null

aa2 = aa1; // just assigns null to aa2
aa1[1] = 1; // allocates and initializes
aa2[1] = 2; // allocates and initializes separate instance
assert(aa1[1] == 1 && aa2[1] == 2);
aa2 = aa1; // NOW they are aliased
assert(aa2[1] == 1);
aa2[1] = 2;
assert(aa1[1] == 2);

-Steve
April 02, 2013
2013/4/2 <luismarques@gmail.com>"@puremagic.com <"\"Luís".Marques">

> On Tuesday, 2 April 2013 at 02:52:48 UTC, Steven Schveighoffer wrote:
>
>> You see, indexing does NOT dereference the pointer, it's an index for that pointer.  c[0] means *(c + 0).  A pointer is essentially an unchecked slice, with undefined length.  This is how it works in C also.
>>
>> c[1] is the same as *(c + 1), completely consistent (and also sets b to
>> 42, 42)
>>
>
> OK, I think I see where I went astray. I was a case of bad induction from
> a few tests :-)
> So, I guess what is happening is the following, right?
>
>     int[2] a;
>
>     int[2] *c;
>     c = &a;
>
>     c[0] = 7;  // same thing as below
>     a = 7;      // same thing above
>

These are doing that element-wise assignment.

    c[0][] = 7;  // same as c[0][0] = c[0][1] = 7;
    a[] = 7;      // same as a[0] = a[1] = 7;

As a side note:
>From 2.063, lack of [] for array operation would be warned with -w switch.

So, there is no "implicit dereferencing".

Kenji Hara


April 02, 2013
On 04/01/2013 05:07 PM, "Luís Marques" <luismarques@gmail.com>" wrote:> Consider this (non-portable) code:

Perhaps it is obvious to everyone but allow me still: It is not non-portable code, but its behavior is undefined.

> {
>      int[2] a;
>      int[2] b;
>      int[2] *c;

c is a pointer to a single int[2].

>      c = &a;
>
>      c[0] = 7;
>      assert(a[0] == 7); // OK, as expected
>
>      c[1] = 42;
>      assert(b[0] == 42); // do we really want this semantics?

A manifestation of undefined behavior.

> But "c[1] = 42" is equivalent to "(*(c+1)) = 42", which might not be
> obvious. In this case it ends writing to b[0].

It seems to be so but it is not defined to work that way.

Ali

April 02, 2013
On Tuesday, 2 April 2013 at 04:48:54 UTC, Steven Schveighoffer wrote:
> HOWEVER, there is one horrible caveat.  You must have assigned an element at least once in order to alias:

Thanks Steve, I had already looked into that, but then got dense and I was treating the AA as having value semantics. I don't need the pointer.

Is there a way to ensure the AA is initialized as not null, besides adding and removing an element? (I couldn't use a literal)

Is the null initializer a consequence of the current opaque implementation, or just of the reference semantics? (I saw on the wiki that there was a desire to use a less opaque implementation. I think the AA ABI should be documented, like the other arrays)

Slightly related, it doesn't seem very reasonable to me that the get method of AAs is not safe:

@safe:

void main()
{
    int[string] x;
    x.get("b", 0);
}

Error: safe function 'D main' cannot call system function 'object.AssociativeArray!(string, int).AssociativeArray.get'
April 02, 2013
On Tue, 02 Apr 2013 01:59:13 -0400, Luís Marques <luismarques@gmail.com> wrote:

> On Tuesday, 2 April 2013 at 04:48:54 UTC, Steven Schveighoffer wrote:
>> HOWEVER, there is one horrible caveat.  You must have assigned an element at least once in order to alias:
>
> Thanks Steve, I had already looked into that, but then got dense and I was treating the AA as having value semantics. I don't need the pointer.
>
> Is there a way to ensure the AA is initialized as not null, besides adding and removing an element? (I couldn't use a literal)

Not sure.  Looking...  can't find anything.

So no.  There really should be.

> Is the null initializer a consequence of the current opaque implementation, or just of the reference semantics? (I saw on the wiki that there was a desire to use a less opaque implementation. I think the AA ABI should be documented, like the other arrays)

The AA ABI is defined by druntime in object.di.  AA's are currently a hybrid between a compiler-magic type and a template.  The plan is to make it completely a template, that is, the compiler simply always treats T[U] as AssociativeArray!(T, U).

The null initializer is a result of D's policy on struct initialization.  Default constructors are not allowed on structs, they must be able to be initialized with static data when declared without a constructor.

> Slightly related, it doesn't seem very reasonable to me that the get method of AAs is not safe:
>
> @safe:
>
> void main()
> {
>      int[string] x;
>      x.get("b", 0);
> }
>
> Error: safe function 'D main' cannot call system function 'object.AssociativeArray!(string, int).AssociativeArray.get'

Someone (can't remember who) went through a lot of effort to try and re-implement AAs as object.AssociativeArray and ran into a lot of problems with things like @safe and pure.  It really should be @safe, but I don't know if there was a specific reason why it wasn't tagged that way.

-Steve