C++ Logo


Advanced search

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

From: Hyman Rosen <hyrosen_at_[hidden]>
Date: Fri, 30 Apr 2021 14:21:42 -0400
Since when is the ability to run your code in the compiler the driving
force behind what C++ should be? Seems like a silly goal.

On Fri, Apr 30, 2021, 11:07 AM Tom Honermann <tom_at_[hidden]> wrote:

> 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]> wrote:
>> On Fri, Apr 30, 2021 at 7:37 AM Giuseppe D'Angelo via Std-Discussion
>> <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]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion

Received on 2021-04-30 13:21:58