carbon-lang icon indicating copy to clipboard operation
carbon-lang copied to clipboard

Consider using underef-type operator '&' for providing 'pointer.field' access for smart pointers

Open OlaFosheimGrostad opened this issue 2 years ago • 7 comments

Let T = decltype(expr) then:

Define *expr be of type deref(T) equivalent to and implicitly convertible to C++ reference type T&.

Define &expr be of type underef(T).

Allow underef to be convertible to a regular address-based pointer so that underef(T) is implicitly convertible to T*

Let expr.name perform auto-dereferencing resulting in the type decltype(deref(T).name) when expr provides a dereference-resolution mechanism (protocol or operator), otherwise let it resolve to decltype(T.name). added: if expr is explicitly dereferenced, i.e. (*expr).name, then auto-dereferencing should be disabled and not used for *expr. (This can be formalized by giving explicit dereferencing a special type xderef that converts to deref in a final normalization step.)

Obtain canonical form by repeatedly substituting:

  • underef(deref(T)) --> T
  • deref(underef(T)) --> T

This is hopefully sufficient to make the following examples work:

    object.field           // access member of non-pointer object
    pointer.field          // access member of pointed to object
    (&pointer).release()   // access member of smart-pointer object
    pointer = pointer      // pointer assignment as in C++
    *pointer = *pointer    // value assignment as in C++

There might be caveats in the context of generic programming.

Added (C++ deref mapping) :

In the case of 'expr.name' where 'expr' is a C++ type, consider in order:

  1. 'expr->name'
  2. '(*expr).name'

(Added: It might be more prudent to not consider the second case, which is '(*expr).name', and only use the first case 'expr->name', as C++ libraries might have abused the operator*() for incompatible purposes.)

In the case where there is a difference the programmer can use:

    (*expr).name       // to get the * operator behaviour
    expr.name          // to get the -> operator behaviour

Added: The rarely used overload for operator->* is not covered here and has to be addressed.

OlaFosheimGrostad avatar Jul 26 '22 10:07 OlaFosheimGrostad

I really like your proposal, this way you can use them like references except for case 4/5 and 5/5. If you would change them like this, wouldn't it be even more consistent?

&pointer = &pointer      // pointer assignment as in C++
pointer = pointer        // value assignment as in C++

sor avatar Jul 26 '22 18:07 sor

If you would change them like this, wouldn't it be even more consistent?

It would indeed be more consistent, which always is a good thing, but what would happen in generic containers? It would be very annoying if I couldn't reuse a set-of-values in order to express a set-of-pointers.

OlaFosheimGrostad avatar Jul 26 '22 18:07 OlaFosheimGrostad

It would indeed be more consistent, which always is a good thing, but what would happen in generic containers? It would be very annoying if I couldn't reuse a set-of-values in order to express a set-of-pointers.

Depends on what you want the pointers to do, if you want an assignment to also be an value assignment (overwriting the object in place) then the code would behave correctly. If you want your container to work on non-pointers, then you would have problems with compiling a *ptr = *ptr expression.

sor avatar Jul 26 '22 18:07 sor

It might be possible to create a wrapper that could make that work, e.g. set(wrapper(pointer_type)), but most C++ programmers would be terribly confused by it.

OlaFosheimGrostad avatar Jul 26 '22 19:07 OlaFosheimGrostad

&pointer = &pointer      // pointer assignment as in C++

This looks weird.

YagaoDirac avatar Jul 28 '22 01:07 YagaoDirac

It seems to me like this is not intending to be a duplicate of #1725 which is focusing on removing the distinction between .Method() on a pointer vs. a pointee.

Instead, it seems to me that this is focused on preserving some distinction, but trying to spell it differently in the syntax?

Have I understood it correctly?

Provided so, I think my key question comes down to this point:

There might be caveats in the context of generic programming.

I feel like this is actually the crux of the question, and I don't really know how to evaluate this without a thoroughly exploration of how this would work in generic code.

Specifically a function like fn F[T:! ..., U:! ... & Deref(T)](x: U) which does not know whether T is a pointer (or smart pointer) type or not. We will have to type-check x.Method(), and will need to know whether to do so against the archetype provided by U vs. the archetype provided by T.

A closely related question, what happens when both a smart pointer and the pointee types provide the same name -- which is used and when? How is the distinction preserved?

If the distinction is not preserved here, then I think this becomes a duplicate of #1725 most likely. But if it is, I'm interested in how we build the rules to do so even in a generic context.

chandlerc avatar Aug 10 '22 02:08 chandlerc

We triage inactive PRs and issues in order to make it easier to find active work. If this issue should remain active or becomes active again, please comment or remove the inactive label. The long term label can also be added for issues which are expected to take time. This issue is labeled inactive because the last activity was over 90 days ago.

github-actions[bot] avatar Nov 09 '22 02:11 github-actions[bot]