Boost logo

Boost :

Subject: Re: [boost] Is there any interest in non-owning pointer-like types?
From: Joseph Thomson (joseph.thomson_at_[hidden])
Date: 2017-02-01 20:15:40

On Thu, Feb 2, 2017 at 6:18 AM, Niall Douglas <s_sourceforge_at_[hidden]>

> On 01/02/2017 07:47, Joseph Thomson wrote:
> > For some time, I have been developing a pair of non-owning pointer-like
> > types that I am currently calling `observer_ptr` and `observer`. I had
> > planned to propose their addition to the C++ standard library, but I have
> > been informed by the author of the original `observer_ptr` proposal
> > <> that
> > the ISO C++ committee has rejected his proposal and made clear that it
> > feels there is no place in the standard library for such types, believing
> > that this role is filled to a satisfactory degree by regular pointers. I
> > wholeheartedly disagree with this assessment, so I am bringing my
> proposal
> > here instead.
> I don't think that exactly accurate. Rather it is that the GSL's
> proposed schema of:
> * owner<T> is an owning pointer
> * span<T> is a borrowed pointer/reference/view
> * C type pointers are non-owning, non-borrowed

As far as I understand it, `span<T>` is a view of objects over a range, in
the vein of `array_view<T>`, and raw pointers are simply non-owning. I'm
not sure what "borrowed" means in this context.

... has general wide agreement in principle, if not in exact
> formulation. The Library Fundamentals TS v2 does have an observer_ptr in
> std::experimental, but there is general lack of sureness as to what it
> actually contributes when a non-annotated pointer according to the GSL
> schema is either an observer by definition, or unupgraded code.

That is just one of the benefits I listed (documentation of intent). The
most benefit important IMO is *type-safety*, followed closely by a more
minimal interface. The `observer_ptr<T>` in `std::experimental` only has
type safety in one direction (conversion to `T*` is explicit), because the
only way to construct it is from `T*`. Observers should preferably be
constructed from `T&`, as this is a type-safe conversion. The
`observer_ptr<T>` in `std::experimental` needs one change to make it type
safe: change `make_observer(T*)` to `make_observer(T&)`.

In my opinion, designating `T*` to mean "observer" would be okay in an
ideal world (it still has far too general an interface for something that
just observes), but the world isn't ideal. Not everyone reads the GSL
guidelines, and those who do won't follow it to the letter, and even if
they did there is still plenty of "unupgraded" code. Also, I think you too
easily dismiss the benefit of being able to distinguish between un-upgraded
and upgraded code; this sounds very useful if you ask me. So, to reiterate
again, `observer_ptr`:

   - Allows upgraded code to be distinguished from un-upgraded code (this
   is more important than you might think)
   - Removes operations that are inappropriate for an observer type
   (pointer arithmetic, array subscript, conversion to `void*`)
   - Provides type safety by allowing construction from `T&` and
   disallowing construction from and conversion to `T*`

Whether to allow implicit/explicit conversion from `T&` is a design detail.
The benefits are there regardless.

Also, it seems to me that the natural meaning of `T*` in C++ is as an
iterator, not an observer:

int arr[] = { 1, 2, 3 };
auto it = std::end(arr); // `decltype(it)` is `int*`

I'd need a fair bit of convincing that observer<T> has merit in any form
> except additional clarity to help demarcate unupgraded code from
> upgraded code. If that's your argument, and you're not doing funny
> things with implicit conversion from T& and other funny non-GSL
> semantics, I'd suppose this though I'd suggest you actually contribute
> it to GSL itself and persuade Neil to let it in.

`observer<T>` is similar to `not_null<T>`, in that it enforces a "not null"
precondition, except `observer<T>` does a much better job because it
enforces the precondition at compile-time.

auto o = make_observer(null_ptr); // compile error: expects `T&`
auto n = not_null<T*>(null_ptr); // compiles fine; run-time error

`observer` forces the user to dereference their pointer, which should be
flagged as unsafe by the static analyser if they don't check for null.
`not_null` waits until compile-time to report the error, and makes it
harder for a static analyser to catch the potential null pointer bug.

I have had a few discussions with Neil over at the GSL GitHub page about
the purpose of `not_null`. Honestly, the design goals of `not_null<T>` seem
unclear to me; it seems to be trying to do too many things at once,
simultaneously trying to be a drop-in replacement for `T*` that enforces a
"not null" precondition at run-time (it's meant to be able to hold any kind
of pointer or pointer-like object), but also trying to be this high-level
modern type that has compile-time type safety (it deletes various pointer
arithmetic operations, and it has been seriously suggested that conversion
from `T` should be explicit). It was initially meant to be compatible with
smart pointers like `unique_ptr`, but this goal seems to have been all but
abandoned after it was realised that this won't really work. It's also
meant to be used in conjunction with strings, despite the fact that
`string_view` is a much better alternative, and with the `owner`
annotation, despite the fact that this will probably have the same
conceptual problems as `unique_ptr`. If my ramblings sound confused, it's
because I am confused, because `not_null` is confused.

The GSL is a *much* better home for it than Boost because then you'll
> have Bjarne batting for it, plus static checking support from Microsoft
> in VS2017 and Google via clang-tidy. You'll also get a *huge* userbase
> almost instantly, because the GSL or rather one of its C++ 98 clones is
> seeing exponential growth recently. It's amazingly useful for upgrading
> ancient C++ codebases.

This would be good. If I brought this to the GSL, I would pretty much be
proposing that `not_null` is scrapped in favour of `observer` and
`observer_ptr`. `not_null<zstring>` is obsoleted by `string_view`;
`not_null<unique_ptr<T>>` etc. are broken; `not_null<owner<T>>` is of
little value IMO, and may be conceptually broken too; honestly, the only
useful case is `not_null<T*>`, and that currently has big issues with its
design too. `observer` and `observer_ptr` are essentially `maybe_null` and
`not_null` with clearer design goals and a fixed interface.

Boost list run by bdawes at, gregod at, cpdaniel at, john at