Search code examples
c++arrayslanguage-lawyerlifetime

Why does a nested array object prevent providing storage?


At Lifetime under Providing storage, it says:

As a special case, objects can be created in arrays of unsigned char or std::byte (since C++17) (in which case it is said that the array provides storage for the object) if

  • the lifetime of the array has begun and not ended
  • the storage for the new object fits entirely within the array
  • there is no array object that satisfies these constraints nested within the array.

Why is the third condition there?

Even if there's a nested array (I believe it means an array which starts at some index of the outer array and ends at an index that's no farther, not an array which is an item of the outer array, as the term nested would probably be understood in most languages), what difficulty does it pose to the standard authors or implementations to enable a segment of the outer array disjoint with the nested array to provide storage?

And, if the nested array doesn't have any array nested in it, do I understand correctly that it can provide storage? If so, why can't the outer array provide storage in the same place, i.e. with the segment providing storage not straddling the start or end of any nested (perhaps indirectly) array?

And, even with straddling, what problems could arise that the standard included this requirement to avoid?

Additionally, can a nested array not satisfy the second condition, i.e. overlap it partially? Can such arrays even exist? In what sense and under ehat conditions is then one nested in another? Can both ever be nested in each other?


Solution

  • Consider this example (ignoring alignment)

    struct A { std::byte s2[64]; };
    std::byte s1[128];
    int* i1 = new(s1) int;
    A* a = new(s1 + 32) A; // Create at some valid offest
    int* i2 = new(a->s2) int;
    

    If we go by the quote you brought, both s1 and a->s2 satisfy the first and second bullet with regard to the integer i2 we created. However, I think we can all agree that it would be more precise to identify a->s2 as providing the storage, since it contains our integer more snuggly (while s1 provides storage for i1). The third bullet does exactly that, i.e. it identifies the "nearest" array object that provides the storage for an object.

    A nested array cannot be one that "starts at some index", because it won't be an array at all. Arrays under the C++ standard are objects with a specific type (size information is baked into them), and shifting the start or end point won't magically create and array in the region (technically we can reinterpret_cast the region, but that quickly has us courting undefined behavior so I wouldn't consider it pertinent to the discussion).