C++ Logo


Advanced search

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

From: Tom Honermann <tom_at_[hidden]>
Date: Fri, 30 Apr 2021 15:35:18 -0400
On 4/30/21 2:21 PM, Hyman Rosen wrote:
> 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.

I didn't state that it is the driving force behind what C++ should be.
That being said, the ability to use constant evaluation in place of the
preprocessor and to use the same language to write code that executes at
compile-time or run-time is a significant benefit.

The point is that this is a benefit of the current object model. And as
Ville noted, that benefit is not limited to the scenario I described.
Any static analysis will benefit from it.


> On Fri, Apr 30, 2021, 11:07 AM Tom Honermann <tom_at_[hidden]
> <mailto: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]
>> <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 14:35:25