std::vector is generally considered to be unimplementable pre-C++20 (as discussed in P0593), as the elements cannot be placed in an internal array while adhering to the required performance restrictions, and pointer-arithmetic on returned pointers and in particular the pointer returned by .data() not being allowed:
char * storage = new char[sizeof(int) * 3];
int * data = new(storage) int(1);
new(storage + sizeof(int)) int(2);
new(storage + sizeof(int) * 2) int(3);
data[2] = 5; // undefined behavior, as there is no array of int at the address data points to
However, the pre-C++17 standard contains these quotes:
[basic.compound] If an object of type T is located at an address A, a pointer of type cv T* whose value is the address A is said to point to that object, regardless of how the value was obtained.
[expr.add] For the purposes of these operators, a pointer to a nonarray object behaves the same as a pointer to the first element of an array of length one with the type of the object as its element type.
[expr.add] Moreover, if the expression P points to the last element of an array object, the expression (P)+1 points one past the last element of the array object, and if the expression Q points one past the last element of an array object, the expression (Q)-1 points to the last element of the array object.
This should make the following code legal and conforming:
char * storage = new char[sizeof(int) * 3];
int * data = new(storage) int(1);
new(storage + sizeof(int)) int(2);
new(storage + sizeof(int) * 2) int(3);
int * tmp = data + 1; // legal due to [expr.add]
// tmp now points to the int placed at storage + sizeof(int) due to [basic.compound]
// requires laundering as of C++17
tmp = tmp + 1; // tmp now points to the int placed at storage + sizeof(int) * 2
*tmp = 5;
(data + 1)[1] = 5; // equivalent to the above
In other words, contiguously stored objects of the same type can reach each other so long as the pointers used to do so are only repeatedly moved step-wise to their immediate neighbour instead of using direct pointer arithmetic. Am I reading the standard correctly?
The C++17 standard changes the formulation of [basic.compound] and [expr.add] and requires laundering the pointer after each stepwise movement in order to actually point to the int object at that location, but should otherwise work equivalently.
Edited to remove the use of reinterpret_cast, as the legality of its use is not the focus of this question.
data()not being allowed" - ...but such arithmetic is guaranteed to work, right? Who says it's not allowed?intobject created in that storage. Pointer arithmetic requires an array object. C++20's implicit object lifetimes are what fixed it (by ratifying, to an extent, what compilers already pretty much did)./dev/random) was barely intended