C++ Logo

std-discussion

Advanced search

Re: On "transparently replaceable" in std::vector operations

From: Tom Honermann <tom_at_[hidden]>
Date: Fri, 30 Apr 2021 11:07:51 -0400
On 4/30/21 10:16 AM, Hyman Rosen via Std-Discussion wrote:
> This is where the conceptual error of the C++ object model shows. A
> pointer or reference points to memory, not to an object. The pointer
> type informs how that memory should be treated. If the memory remains
> allocated to the program, the pointer is valid. If the contents of the
> memory are valid for a type, the object of that type in that memory is
> valid.

I tried to point this out elsewhere in this thread, but I'm not sure the
point landed.

If the C++ object model worked as you state it should, then constexpr
evaluation would not be feasible or would be more constrained than it
currently is. With a memory based model, the implementation would have
to mimic memory layout for the target architecture in order for code to
behave the same at compile-time vs run-time. The C++ object model
avoids that by allowing the implementation to represent objects
differently during constant evaluation, but in a way that isn't
observable. This does put some constraints on what operations can be
performed at compile-time (e.g., no reinterpret_cast), but allows a
clear subset of the language to be used in both evaluation contexts.

Just to make that a little more concrete, here is an example that mimics
a vector insert operation (including use of placement new as would be
done by std::allocator). All major compilers reject the example. If
constexpr is removed from the declaration of f()::v, some compilers
still reject it (I assume the ones that accept it do so as a conforming
extension, but I'm not sure). For those that accept it, they do
(currently) behave as you describe; the dereference of the pointer
yields the value of the replacement object, including under optimization.

https://www.godbolt.org/z/Kffzbn8jh

#include <cassert>
#include <utility>
#include <new>
using T = int;
constexpr T insert(T i) {
   T a[3] = { 1, 3 };
   T *p = &a[1];
   a[2] = std::move(a[1]);
   new (&a[1]) T{i};
   return *p;
}
void f() {
   constexpr T v = insert(2);
   assert(v == 2);
}

It would perhaps be feasible to make the above example work in limited
circumstances if we allowed placement new to replace an existing object
of the same type (that is not so different from an assignment after
all). C++20 constexpr vector support avoids this question by special
casing std::allocator at compile-time.

Tom.

>
> Your deciding for all programmers that when they have pointers to
> vector elements they "really" mean some theoretical element that
> wanders around within the vector is no more valid than if you decided
> the same for pointers to array elements. You are breaking people's
> code or making it harder for them to work so that you can satisfy
> yourself about some useless abstract principle being followed.
>
> On Fri, Apr 30, 2021, 9:33 AM Jason McKesson via Std-Discussion
> <std-discussion_at_[hidden]
> <mailto:std-discussion_at_[hidden]>> wrote:
>
> On Fri, Apr 30, 2021 at 7:37 AM Giuseppe D'Angelo via Std-Discussion
> <std-discussion_at_[hidden]
> <mailto:std-discussion_at_[hidden]>> wrote:
> >
> > Hello,
> >
> > On 30/04/2021 01:56, Jason McKesson via Std-Discussion wrote:
> > >
> > > By inserting an object into a vector, you have conceptually
> changed
> > > the address of all of the objects past that one in the container.
> > > However, any pointers/references cannot themselves move to aim
> at the
> > > correct object. Therefore, such pointers/references should no
> longer
> > > be considered valid pointers/references to what they pointed
> > > to/referenced.
> > >
> > > Put simply, a pointer/reference to an element in a container
> is not,
> > > and should not be considered, a fancy index.
> >
> > I never said that pointers/references/iterators should magically
> move.
> > In fact, I said that pointers would point to the element that
> ends up
> > being in the position they were pointing to.
>
> My point is that they *should* "move". A pointer to an element should
> be a pointer to that element and no other element, unless you
> explicitly modify that element. Because the nature of `vector` makes
> that impossible implementation-wise, the only reasonable alternative
> is to make them invalid.
>
> > You're basically arguing that "it is what it is" regarding the
> lack of
> > such a guarantee. That is, the mental model that `vector` to go
> with is
> > a mental model that invalidates pointers, even if:
> >
> > * such invalidation is unenforceable (unlike iterators);
> > * such invalidation doesn't actually happen in any
> implementation (are
> > implementations allowed to have extra capacity on the left to
> optimize
> > prepend? That makes a mess out of capacity(), but maybe there's an
> > escape hatch/hat trick);
> > * such invalidation cannot even happen because of (new? old?)
> language
> > rules (unlike iterators, and due to a lack of an ad-hoc
> > `std::invalidate` facility) and therefore it's not usable in
> > optimizations, sanitizers, etc.;
> > * it costs precisely zero for an implementation provide the extra
> > guarantee (in fact, is it already "accidentally" offered today
> by all
> > implementations?).
> >
> > I'm not sure if I'm 100% convinced. A `vector` isn't an
> arbitrary data
> > structure, it's a quite specific one, with clear operational
> semantics.
>
> But it is a container, and it should act like one. Pointers to
> elements in a container should act like pointer to elements in every
> other container, except for when that is made impossible by some
> specific nature of a specific container. And in those cases, those
> pointers should be invalidated; they shouldn't do something weird like
> magically transform into pointers to other elements.
>
> And I'd like to point out that there's not really any practical
> benefit to creating a special rule like this for `vector`. That is, it
> doesn't let you do anything you couldn't do with indices if you know
> you're working with a `vector`. So you should just use indices to make
> it clear to everyone reading your code that you expect the underlying
> elements to shift around.
>
> In any case, you wanted to understand the conceptual reasoning behind
> it, and this is it. You don't have to agree with it, but this is the
> logic behind it.
> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> <mailto:Std-Discussion_at_[hidden]>
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>
>


Received on 2021-04-30 10:07:55