Is pointer arithmetic on allocated storage allowed since C++20?
Asked Answered
H

1

15

In the C++20 standard, it is said that array types are implicit lifetime type.

Does it mean that an array to a non implicit lifetime type can be implicitly created? The implicit creation of such an array would not cause creation of the array's elements?

Consider this case:

//implicit creation of an array of std::string 
//but not the std::string elements:
void * ptr = operator new(sizeof (std::string) * 10);
//use launder to get a "pointer to object" (which object?)
std::string * sptr = std::launder(static_cast<std::string*>(ptr));
//pointer arithmetic on not created array elements well defined?
new (sptr+1) std::string("second element");

Is this code not UB any more since C++20?


Maybe this way is better?

//implicit creation of an array of std::string 
//but not the std::string elements:
void * ptr = operator new(sizeof (std::string) * 10);
//use launder to get a "pointer to object" (actually not necessary)
std::string (* sptr)[10] = std::launder(static_cast<std::string(*)[10]>(ptr));
//pointer arithmetic on an array is well defined
new (*sptr+1) std::string("second element");

TC Answer + Comments conclusion:

  1. Array elements are not created but the array is created
  2. The use of launder in the first example cause UB, and is not necessary in the second example.

The right code is:

    //implicit creation of an array of std::string 
    //but not the std::string elements:
    void * ptr = operator new(sizeof (std::string) * 10);
    //the pointer already points to the implicitly created object
    //so casting is enough 
    std::string (* sptr)[10] = static_cast<std::string(*)[10]>(ptr);
    //pointer arithmetic on an array is well defined
    new (*sptr+1) std::string("second element");
Hildegardhildegarde answered 11/3, 2020 at 7:39 Comment(19)
I've just done a search through the (draft) C++20 standard, and found nothing that describes arrays as an "implicit lifetime type" (and, yes, I searched for variations). Please provide a more detailed description of your claim (e.g. section and clause in the standard). Bit hard to answer your question without being able to find the source, let alone any relevant context.Rubetta
@Peter: eel.is/c++draft/basic.types#9, last sentenceBriard
I was looking at the PDF open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4849.pdf (ostensibly the latest working draft) and it doesn't even have that sentence. Looks like you'll need to find the meaning of "implicit-lifetime" too. I suspect your link may have picked up some "edits in progress" that haven't even made it into released working drafts.Rubetta
@Rubetta The changes are the result of P0593 being merged into the standard from the recent Prague meeting. They haven't yet released the resulting draft yet, but you can see the merged wording in this commit.Fennessy
@Hildegardhildegarde where's the quote says that "void * ptr = operator new(sizeof (std::string) * 10);" will create an array of type std::string? [expr.new] does not say that.Zwick
@Hildegardhildegarde According to the comments in the answer, std::launder is not necessary in the second version and causes UB in the first version. May be edit needed to make the code valid? It is still not 100% percent clear which version is correct - if both, perhaps state that explicitly or in the answer and also clarify which version is "idiomatic"?Yingling
@YuvalK Did it. Fill free to improve the added conclusion.Hildegardhildegarde
@Hildegardhildegarde - just want to clarify. Does it mean that first option is not correct? Or since an array of std::string is created, the language allows to deduce that static_cast<std::string*> points to the first element of the array and not just random string? Or only the second variant is valid?Yingling
@YuvalK The first is not correct because of std::launder. Without std::launder it would be correct.Hildegardhildegarde
@Hildegardhildegarde - std::string (* sptr)[10] = static_cast<std::string(*)[10]>(ptr); is that it doesn't scale if you want to replace 10 with n - in order to dynamically allocate memory.Yingling
@YuvalK Yes in case of dynamic memory allocation size this line should be replaced by std::string * sptr = static_cast<std::string *>(ptr)Hildegardhildegarde
Now, what happens if you use placement new, as in the example of allocate(n) in the standard? eel.is/c++draft/allocator.requirements#general-2 launder(reinterpret_­cast<T*>(new (p) byte[n * sizeof(T)])) is not a legal use of launder according to the comments to the answer (no T is created...). On the other hand, you run into problems without launder either, as far as I know. Standard is not clear whether launder can be used to legalize pointer arithmetic - as far as I am aware it doesn't state that.Yingling
@YuvalK Could this example predate C++20?Hildegardhildegarde
No. It appears in p0593r6 as well, as one of the suggested changes. open-std.org/jtc1/sc22/wg21/docs/papers/2020/…. The code snippet is new.Yingling
@YukalK allocate(n) would not produce a a pointer to a suitable created object. but malloc would do?Hildegardhildegarde
When did someone say that?Yingling
@YuvalK Nobody probably. allocate(n) return a pointer to suitably allocated object, The exemple given in open-std.org/jtc1/sc22/wg21/docs/papers/2020/… show how to reuse storage, but not how to use the resulting pointer of the allocation function. This is common in the c++ standard, examples are not used to illustrate the specification but to show how brilliant is the author when he faces unrelated corner case.Hildegardhildegarde
My impression, that it was an example of "how allocate(n) might be implemented" - as these are requirements allocator requirements. This is not an issue for the standard std::allocator<T> - they are allowed to be "magic" and "implementation defined".Yingling
@YuvalK The exemple begin with "Example: When reusing storage denoted by some pointer value p". reusing storage is defined in basic/lifetime. The first use of the pointer return by allocate(n) is not a storage result. For a storage to be reused it must be occupied by an alived object (and not be an array of byte type or unsigned char type => see basic/object). I think the standard lacks editorials constraints.Hildegardhildegarde
N
5

Does it means that an array to a non implicit lifetime type can be implicitly created?

Yes.

The implicit creation of such an array would not cause creation of the array's elements?

Yes.

This is what makes std::vector implementable in ordinary C++.

Nun answered 11/3, 2020 at 15:35 Comment(6)
Could you confirm also that std::launder(static_cast<std::string*>(ptr)) does not return a pointer to the first element of the array because it is not within its lifetime, but that std::launder(static_cast<std::string(*)[10]>(ptr)) return a pointer to the array, because the array is within its lifetime?Hildegardhildegarde
@Hildegardhildegarde And I suppose the std::launder isn't actually needed, because eel.is/c++draft/intro.object#11 guarantees that ptr will already point to the array?Fennessy
@walnut, I missed that. So a static_cast to std::string (*) [10] should be sufficient! tx.Hildegardhildegarde
@Hildegardhildegarde But I guess the question then becomes whether your first example without the std::launder will be well-defined. There is no std::string object to point to, but ptr could point to the array, so that the static cast will leave the value unchanged and sptr will point to the array as well. With std::launder it is UB simply because of std::launder's requirements.Fennessy
@Fennessy Indeed, the necessity of this circumvolution was std::launder. Cool that makes the code much more readable.Hildegardhildegarde
Does it mean that both std::string * sptr = static_cast<std::string*>(ptr) and std::string (* sptr)[10] = static_cast<std::string(*)[10]>(ptr) are valid? Or only the static_cast<std::string(*)[10]>(ptr) version? If both are valid, which one is preferred?Yingling

© 2022 - 2024 — McMap. All rights reserved.