14

The current draft standard (and presumably C++17) say in [basic.compound/4]:

[ Note: An array object and its first element are not pointer-interconvertible, even though they have the same address. — end note ]

So a pointer to an object cannot be reinterpret_cast'd to get its enclosing array pointer.

Now, there is std::launder, [ptr.launder/1]:

template<class T> [[nodiscard]] constexpr T* launder(T* p) noexcept;

Requires: p represents the address A of a byte in memory. An object X that is within its lifetime and whose type is similar to T is located at the address A. All bytes of storage that would be reachable through the result are reachable through p (see below).

And the definion of reachable is in [ptr.launder/3]:

Remarks: An invocation of this function may be used in a core constant expression whenever the value of its argument may be used in a core constant expression. A byte of storage is reachable through a pointer value that points to an object Y if it is within the storage occupied by Y, an object that is pointer-interconvertible with Y, or the immediately-enclosing array object if Y is an array element. The program is ill-formed if T is a function type or cv void.

Now, at first sight, it seems that std::launder is can be used to do the aforementioned conversion, because of the part I've put emphasis.

But. If p points to an object of an array, the bytes of the array is reachable according to this definition (even though p is not pointer-interconvertible to array-pointer), just like the result of the launder. So, it seems that the definition doesn't say anything about this issue.

So, can std::launder be used to convert an object pointer to its enclosing array pointer?

Community
  • 1
  • 1
geza
  • 28,403
  • 6
  • 61
  • 135
  • "Reachable" here is a terminology. The latter part of the "remarks:" paragraph is only an interpretation/definition of the terminology "reachable" in the "requires:" paragraph. – xskxzr Jul 27 '18 at 14:01
  • 5
    In case of array, "reachable" refers to simple pointer arithmetic like `p + 1`. You can reach any element from any other element. Pointer interconvertibility though refers to conversion to pointer *to the entire array*, like `int (*) [10]`, which is a completely different story. You don't need this interconvertibility to reach other elements. – AnT stands with Russia Jul 27 '18 at 16:36
  • @AnT: thanks for the clarification! – geza Jul 27 '18 at 21:45

2 Answers2

11

This depends on whether the enclosing array object is a complete object, and if not, whether you can validly access more bytes through a pointer to that enclosing array object (e.g., because it's an array element itself, or pointer-interconvertible with a larger object, or pointer-interconvertible with an object that's an array element). The "reachable" requirement means that you cannot use launder to obtain a pointer that would allow you to access more bytes than the source pointer value allows, on pain of undefined behavior. This ensures that the possibility that some unknown code may call launder does not affect the compiler's escape analysis.

I suppose some examples could help. Each example below reinterpret_casts a int* pointing to the first element of an array of 10 ints into a int(*)[10]. Since they are not pointer-interconvertible, the reinterpret_cast does not change the pointer value, and you get a int(*)[10] with the value of "pointer to the first element of (whatever the array is)". Each example then attempts to obtain a pointer to the entire array by calling std::launder on the cast pointer.

int x[10];
auto p = std::launder(reinterpret_cast<int(*)[10]>(&x[0])); 

This is OK; you can access all elements of x through the source pointer, and the result of the launder doesn't allow you to access anything else.

int x2[2][10];
auto p2 = std::launder(reinterpret_cast<int(*)[10]>(&x2[0][0])); 

This is undefined. You can only access elements of x2[0] through the source pointer, but the result (which would be a pointer to x2[0]) would have allowed you to access x2[1], which you can't through the source.

struct X { int a[10]; } x3, x4[2]; // assume no padding
auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x3.a[0])); // OK

This is OK. Again, you can't access through a pointer to x3.a any byte you can't access already.

auto p4 = std::launder(reinterpret_cast<int(*)[10]>(&x4[0].a[0])); 

This is (intended to be) undefined. You would have been able to reach x4[1] from the result because x4[0].a is pointer-interconvertible with x4[0], so a pointer to the former can be reinterpret_cast to yield a pointer to the latter, which then can be used for pointer arithmetic. See https://wg21.link/LWG2859.

struct Y { int a[10]; double y; } x5;
auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x5.a[0])); 

And this is again undefined, because you would have been able to reach x5.y from the resulting pointer (by reinterpret_cast to a Y*) but the source pointer can't be used to access it.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • 1
    So according to this reasoning, somehow the _source_ of the expression affects the semantics of `std::launder`? That sounds insane as a mental model, regardless how optimizers are implemented. Reminds me of the whole pointer provenance thing. – Passer By Jul 27 '18 at 17:33
  • Perhaps `std::launder` is precisely for those situations where the "mental model" _is_ insane ;-) – André Jul 27 '18 at 18:25
  • The specification of `std::launder` says nothing at all about the argument of `reinterpret_cast`, or indeed about `reinterpret_cast`. It only states things about the argument and the result of `std::launder`. That is, in the cases you consider, about the *result* of `reinterpret_cast`. – n. m. could be an AI Jul 27 '18 at 19:49
  • @n.m. In all the examples above, the `reinterpret_cast` doesn't change the pointer value. It remains "pointer to the first element of (whatever the array is)". – T.C. Jul 27 '18 at 21:25
  • Yes, but it is **not** "a pointer to the array as a whole", despite having the same value. You need `std::launder` to make it so. That's the entire point. – n. m. could be an AI Jul 27 '18 at 21:27
  • Right, and the point of the examples is to illustrate when you can and cannot launder. – T.C. Jul 27 '18 at 21:27
  • The problem here is that you are trying to make a point based on the false premises/misread wording of the standard. The result of `std::launder(reinterpret_cast(&x2[0][0]));` does not depend on what you are allowed to do with `&x2[0][0]`. It only depends on the type and the *value* of the argument of `std::launder`, which is the result of `reinterpret_cast`. – n. m. could be an AI Jul 27 '18 at 21:41
  • The *value* of the argument is "pointer to `x2[0][0]`"; the attempt is to launder it into "pointer to `x2[0]`", which is disallowed because the bytes comprising `x2[1]` is reachable from the latter value but not former. I'm not understanding what you are complaining about. – T.C. Jul 27 '18 at 21:45
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/176911/discussion-between-n-m-and-t-c). – n. m. could be an AI Jul 27 '18 at 21:45
  • Thanks for the answer T.C., great explanation! – geza Jul 28 '18 at 09:10
2

Remark: any non schizophrenic compiler will probably gladly accept that, as it would accept a C-style cast or a re-interpret cast, so just try and see is not an option.

But IMHO, the answer to your question is no. The emphasized immediately-enclosing array object if Y is an array element lies in a Remark paragraph, not in the Requires one. That means that provided the requires section is respected, the remarks one also applies. As an array and its element type are not similar types, the requirement is not satisfied and std::launder cannot be used.

What follows is more of a general (philosophycal?) interpretation. At the time of K&R C (in the 70's), C was intended to be able to replace assembly language. For that reason the rule was: the compiler must obey the programmer provided the source code can be translated. So no strict aliasing rule and a pointer was no more that an address with additional arithmetics rules. This strongly changed in C99 and C++03 (not speaking of C++11 +). Programmers are now supposed to use C++ as a high level language. That means that a pointer is just an object that allows to access another object of a given type, and an array and its element type are totally different types. Memory addresses are now little more than implementation details. So trying to convert a pointer to an array to a pointer to its first element is then against the philosophy of the language and could bite the programmer in a later version of the compiler. Of course real life compiler still accept it for compatibility reasons, but we should not even try to use it in modern programs.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • 1
    An array is located at the same address as its first element, and the array type is definitely similar to itself, so the requires section does apply. – n. m. could be an AI Jul 27 '18 at 09:14
  • @n.m.: just to clarify: do you mean, that the answer is "Yes"? – geza Jul 27 '18 at 09:34
  • Yes I think so. Disclaimer: this was stated in an answer (deleted by a moderator) to [this question of mine](https://stackoverflow.com/questions/47924103/pointer-interconvertibility-vs-having-the-same-address). – n. m. could be an AI Jul 27 '18 at 10:01
  • @n.m. : I've read the deleted answer you cite. I do not agree with it, but IMHO it is an answer an not a comment. So I wonder whether it has not been wrongly deleted. Would you mind if I ask on meta about it? – Serge Ballesta Jul 27 '18 at 11:47
  • @n.m.: I have just asked a [question](https://meta.stackoverflow.com/q/371664/3545273) about that on meta... – Serge Ballesta Jul 27 '18 at 13:55
  • @LanguageLawyer I tried to edit your post to make it an answer, but it changes it too much for a third person edit. If you agree with my edits, add a new edit to make clear that you accept that and ping me in a comment so that I can ask for the post to be undeleted. – Serge Ballesta Jul 27 '18 at 16:21
  • @geza I'm no longer sure the answer is yes. – n. m. could be an AI Jul 27 '18 at 22:20