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.

Tom.


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