...but what I want to do depends on being able to form well-defined pointer subtractions (to std::ptrdiff_t) between bytes within the object representation in the same object, or usually just between an arbitrary byte and the base address, i.e. given
I want to be able to guarantee that myOffset will hold the distance in unsigned char between someObj.someMember and the base address of someObj
However, I don't think the above guarantees specifically allow this. And I'm not sure the wording elsewhere that any object can be considered as a 'sequence' of unsigned char - the object representation - formally allows said sequence to be considered as an array in order to get around the 1st allowed piece of arithmetic earlier... or that there's any other allowance making this well-defined.
Anyhow, TL;DR, the questions:
If the decision is to retain this rule I believe that, at minimum, an exception should be added to make it possible to treat an object as an array of
unsigned char
, otherwise there's would be no way to implement user-defined variants ofmemset()
ormemcpy()
without violating this standard.But I do still think this rule is overly restrictive and the decision to prohibit treating non-array objects of any type equivalently to arrays of a single element should be reconsidered. To avoid violating the rule, programmers whose code is subject to this standard would have to define all objects that might be potentially treated as arrays as arrays of size 1. That's an infeasible requirement in systems consisting of libraries or modules whose public APIs expose individual objects.
On Thu, Aug 18, 2016 at 10:58 AM, D. B. <db0...@gmail.com> wrote:> However, I don't think the above guarantees specifically allow this. And I'm not sure the wording elsewhere that any object can be considered as a 'sequence' of unsigned char - the object representation - formally allows said sequence to be considered as an array in order to get around the 1st allowed piece of arithmetic earlier... or that there's any other allowance making this well-defined.Per [intro.object]/5, "An object of trivially copyable or standard-layout type (3.9) shall occupy contiguous bytes of storage."Per [basic.types]/2, "For any object (other than a base-class subobject) of trivially copyable type T, whether or not the object holds a valid value of type T, the underlying bytes (1.7) making up the object can be copied into an array of char or unsigned char. 42 If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value."
I may still be mistaken, but for me that implies that pointers to at least trivially copyable types can be recast as pointers into an array of unsigned chars and operated safely while they stay within the storage occupied by the object. This can probably be extended to cover standard-layout types.
On Thu, Aug 18, 2016 at 1:02 PM, D. B. <db0...@gmail.com> wrote:> In relation to the above, much of my doubt centres around some key specifiers here: can be copied into an array of char or unsigned char and copied back – not that the object itself, considered in place without copying, is (can be treated as) such an array.reinterpret_cast between two pointer types (via the requirements imposed on static_cast) results in "the same address as the original pointer value". The result is also a safely derived pointer.
It is therefore safe and well defined to convert from any type (I am neglecting alignment complications here and in what follows) to a pointer to an array of unsigned characters.
On Thu, Aug 18, 2016 at 2:32 PM, Bo Persson <b...@gmb.dk> wrote:
On 2016-08-18 12:53, D. B. wrote:
On Thu, Aug 18, 2016 at 11:47 AM, Bo Persson <b...@gmb.dk
<mailto:b...@gmb.dk>> wrote:
I think this fails already here, because the reinterpret_cast is not
guaranteed to work. For example, on a word addressed machine, a char
pointer might need a part-word index in addition to the word address.
Surely it must work, since the Standard provisions for char* and
unsigned char* to be able to alias any other type - which we must assume
from context is so that they can read object representation?
The missing guarantee is sizeof(&someObj) == sizeof(char*), which is not required.
So if char* (and void*) is larger than int* or some_class*, a reinterpret_cast will likely not work.
Why would the Standard explicitly allow aliasing via [unsigned] char lvalues to objects of different type, if it was UB to ever form such an lvalue in the first place?I'm not sure why the sizeof different pointer types matters. A reinterpret_cast should be capable of doing any manipulation it wants to the numerical values of a pointer, as long as it ends up pointing to the same thing when converted to the destination type (and back). I'm not sure the sizeof is relevant. We can convert between other types with different sizes just fine; why not pointers? As long as no information is lost that can't be reconstituted between converting the numerical representations, I see no problem. Assuming that's what you mean, can you elaborate on precisely where such information could be lost irrecoverably?
An object pointer can be explicitly converted to an object pointer of a different type.72 When a prvalue v of object pointer type is converted to the object pointer type “pointer to cv T”, the result is static_cast<cv T*>(static_cast<cv void*>(v)). Converting a prvalue of type “pointer to T1” to the type “pointer to T2”
(where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value.
A glvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The result refers to the same object as the source glvalue, but with the specified type.
On Thu, Aug 18, 2016 at 2:08 PM, D. B. <db0...@gmail.com> wrote:> This is where it gets unclear for me. The aliasing rules make it clear that we can convert a pointer to any object to an [unsigned] char *, which by inference is so that object representation can be read. But it never specifically says that the resulting char * can be considered to be part of a conceptual array of size sizeof(Obj).That part is easy. We can convert that to a pointer to unsigned char[sizeof object]. Then we can certainly access every element of that array.The nasty part is that you want an offset, which means you would end up with two pointers to arrays, different arrays, so you still cannot do arithmetic with them directly. What I think you can do is take the address of the complete object, cast it to pointer to array of unsigned chars, get the address of the first element of that array, then increment the resultant pointer until it becomes equal to the address of the member of interest (that is cast to pointer to unsigned char).
That should work because each byte is required to have a unique address (so different addresses cannot refer to the same byte), and the comparison for equality of pointers compares the addresses they represent.
Either that, or the standard offsetof macro.
Or we could require that the specialization of std::distance for pointer types works for any two (aligned) pointers within the same region of storage, not just within the same array. So your pointer_distance, but with an existing name. This should be reasonably uncontroversial, seeing as we already have std::less specialized on pointer types providing a stable, equality-respecting order for any two pointers anywhere in memory.
As an aside, std::less<T*> means that you can find the offset of a member in O(log n) i.e. O(sizeof(size_t)), using binary search!
Or we could require that the specialization of std::distance for pointer types works for any two (aligned) pointers within the same region of storage, not just within the same array. So your pointer_distance, but with an existing name. This should be reasonably uncontroversial, seeing as we already have std::less specialized on pointer types providing a stable, equality-respecting order for any two pointers anywhere in memory.
It's not solely of historical interest, though. Optimizers can and do take advantage of the strict rules for pointer arithmetic and order comparison, so I think there would be considerable resistance to relaxing them.
Also, flat memory might be in the ascendant now, but who knows what the future holds? Perhaps strict pointer arithmetic and order comparison will be of advantage programming future heterogeneous architectures.
Thanks, I'm convinced by both your points.
I guess the difference between this and std::less<T*> is that the latter is essential to disarm a landmine in the Library, namely using pointers as the key type in associative containers, while computing offsets at runtime is far more a niche use case.
If we're talking pointers of arbitrary type, there's the optimization issue, but also that one of the pointers might not be aligned - think #pragma pack. If on the other hand you're only concerned about character pointers, then optimization and alignment cease to be issues.
Also consider that currently it would be allowable for the largest possible object to be larger than the largest possible array of non-character type. That would no longer work the same if we were able to subtract and compare arbitrary non-character pointers within the same object.
Note that if your object is constructed into user-visible storage then there already exists a character array beneath it and you can use std::launder on appropriate character pointers to retrieve pointers into the character array.
If we're talking pointers of arbitrary type, there's the optimization issue, but also that one of the pointers might not be aligned - think #pragma pack. If on the other hand you're only concerned about character pointers, then optimization and alignment cease to be issues
Also consider that currently it would be allowable for the largest possible object to be larger than the largest possible array of non-character type. That would no longer work the same if we were able to subtract and compare arbitrary non-character pointers within the same object
Note that if your object is constructed into user-visible storage then there already exists a character array beneath it and you can use std::launder on appropriate character pointers to retrieve pointers into the character array.
[ Footnote: An object that is not an array element is considered to belong to a single-element array for this purpose; see 5.3.1. A pointer past the end of the last element of an array of n elements is considered to point to a hypothetical element n for this purpose; see 3.9.2. ],
Looking at this about launder et al:This addition is intriguing:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4430.html[ Footnote: An object that is not an array element is considered to belong to a single-element array for this purpose; see 5.3.1. A pointer past the end of the last element of an array of n elements is considered to point to a hypothetical element n for this purpose; see 3.9.2. ],
So, does this mean that...
- We can cast an object to unsigned char *, either - adjust to taste - because said object contains an unsigned char in 1st member position, or by some interpretation of the object rep/aliasing rules
- So we now have an unsigned char* p
- ...which can act like an unsigned char q[1], so we now have &q[0] == *(p + 1)
- ...to which we can point 1-past the end and get a conceptual larger array, having 2 elements, in which &q[1] == *(p + 2)
- ...from which we can point 1-past the end ...etc.... &q[2] == *(p + 3)
- ...and repeat indefinitely?
No, sorry. What you have is a contiguous sequence of unsigned char[1], as e.g. if they were adjacent data members of a struct.
That is, unless such an array of characters already exists as it would if e.g. your object were constructed in storage provided by std::aligned_storage, or by a user-defined allocator.
No, sorry. What you have is a contiguous sequence of unsigned char[1], as e.g. if they were adjacent data members of a struct.
That is, unless such an array of characters already exists as it would if e.g. your object were constructed in storage provided by std::aligned_storage, or by a user-defined allocator.
On Sat, Aug 20, 2016 at 3:06 PM, 'Edward Catmur' via ISO C++ Standard - Future Proposals <std-pr...@isocpp.org> wrote:No, sorry. What you have is a contiguous sequence of unsigned char[1], as e.g. if they were adjacent data members of a struct.
That is, unless such an array of characters already exists as it would if e.g. your object were constructed in storage provided by std::aligned_storage, or by a user-defined allocator.
I really appreciate your efforts to think of legal loopholes that might be able to make this well-defined for me. :-) Is std::aligned_storage defined to use an array of char, or can it do any implementation-defined acrobatics it wishes? If the answer is affirmative, that would be a lot easier than writing a custom allocator just to stay on the Standard's good side...
template<std::size_t Len, std::size_t Align = alignof(std::aligned_storage_t<Len>)> struct my_aligned_storage { typedef struct { alignas(Align) unsigned char data[Len]; } type; };
That part is easy. We can convert that to a pointer to unsigned char[sizeof object]. Then we can certainly access every element of that array.The nasty part is that you want an offset, which means you would end up with two pointers to arrays, different arrays, so you still cannot do arithmetic with them directly.
On Sat, Aug 20, 2016 at 11:26 AM, D. B. <db0...@gmail.com> wrote:> I get all the points about different types of pointers, memory regions, etc. - which are valid counterarguments for totally arbitrary subtractions. But I don't don't grasp why such caveats should disallow (in fact: don't logically allow) arithmetic within the same (trivially copyable/standard layout) object.. Such an object must occupy a single, known region of storage. Just as is true for arrays.Let me try again.(0) [intro.memory]/1 "Every byte has a unique address."(1) [intro.object]/5 "An object of trivially copyable or standard-layout type (3.9) shall occupy contiguous bytes of storage."(2) [basic.compound]/3 "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."
(3) [basic.types]/9 "Scalar types, standard-layout class types (Clause 9), arrays of such types and cv-qualified versions of these types (3.9.3) are collectively called standard-layout types."(4) [basic.align]/6 "Furthermore, the narrow character types (3.9.1) shall have the weakest alignment requirement."(5) [conv.ptr]/2 "A prvalue of type “pointer to cv T”, where T is an object type, can be converted to a prvalue of type “pointer to cv void”. The result of converting a non-null pointer value of a pointer to object type to a “pointer to cv void” represents the address of the same byte in memory as the original pointer value"(6) [expr.static.cast]/13 "A prvalue of type “pointer to cv1 void” can be converted to a prvalue of type “pointer to cv2 T”, where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. The null pointer value is converted to the null pointer value of the destination type. If the original pointer value represents the address A of a byte in memory and A satisfies the alignment requirement of T, then the resulting pointer value represents the same address as the original pointer value, that is, A"(7) [expr.reinterpret.cast]/7 "An object pointer can be explicitly converted to an object pointer of a different type. When a prvalue v of object pointer type is converted to the object pointer type “pointer to cv T”, the result is static_cast<cv T*>(static_cast<cv void*>(v))."(8) [expr.sizeof] "The sizeof operator yields the number of bytes in the object representation of its operand. The operand is either an expression, which is an unevaluated operand (Clause 5), or a parenthesized type-id. The sizeof operator shall not be applied to an expression that has function or incomplete type, to the parenthesized name of such types, or to a glvalue that designates a bit-field. sizeof(char), sizeof(signed char) and sizeof(unsigned char) are 1."Assume types A and C are trivially copyable or standard-layout types, type C has field a of type A, which is not a bit-field.Per (5), (6) & (7), pac defined viaauto pac = reinterpret_cast<unsigned char(*)[sizeof (C)]>(&c);is a pointer representing the address of the first byte of c.Per (2), pc defined viaauto pc = &(*pac)[0]is a pointer to the first unsigned char in the array pointed to by pac, and it represents the address of c.
Per (5), (6) & (7), pa defined viaauto pa = reinterpret_cast<unsigned char*>(&c.a);is a pointer representing address X of the first byte of c.a.Per (0) & (1), X is an address of a byte somewhere in the storage occupied by the array pointed to by pac. There must therefore be an unsigned char in the array pointed to by pac, with address X. Assume the latter is not true, then, per (8), there is exactly one byte per any unsigned character, therefore the byte at address X does not correspond to any byte representing any unsigned char in the array pointed by pac, which contradicts either (1) or (4). Therefore, an unsigned char in the array pointed to by pac exists at address X. Per (2), pa, representing address X, points to an unsigned char in the array pointed to by pac.Therefore, pc and pa are both pointers to unsigned chars in the array pointed to by pac.Per (2), we should achieve the same if pc is defined directly viaauto pc = reinterpret_cast<unsigned char*>(&c);
On Fri, Aug 26, 2016 at 4:30 PM, 'Edward Catmur' via ISO C++ Standard - Future Proposals <std-pr...@isocpp.org> wrote:> Until you've read P0137R1 (and, unless you're smarter than me, reread it multiple times) what I have to say here will be surprising, so please bear that in mind. Per [intro.object]/1 and the associated drafting note, an array of N unsigned char is *not* created when you obtain storage for an object type T, sizeof(T) == N. This qualifies [basic.life]/1, since if an object is not created, its lifetime cannot begin even though appropriate storage has been obtained.The current standard says there "An object is a region of storage", so creating an object means "creating a region of storage", it does not mean creating a "type", which suggests that the type is subject to interpretation of the storage. The interpretation is reinforced by [expr.reinterpret.cast]/11, which says: "A glvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The result refers to the same object as the source glvalue, but with the specified type. [...] No temporary is created, no copy is made, and constructors (12.1) or conversion functions (12.3) are not called."Observe that with the current definition "object is storage", there is no contradiction, because we can meaningfully speak of "same object, different type". This is why pac in my previous messages exists (in the current standard) even though it was not created as an object of an array type.P0137R1 changes that to "An object occupies a region of storage...", and it is not clear what "creates" means. We do not know what an object is. That's a pretty big defect, I'd say. This creates a problem with [expr.reinterpret.cast]/11 cited above, because it is impossible to say what it means and whether it really means anything.
Note finally that in C the notion of the object is consistent with the current C++:3.151 objectregion of data storage in the execution environment, the contents of which can represent values2 NOTE When referenced, an object may be interpreted as having a particular type; see 6.3.2.1.(end)It says very clearly that the type is an interpretation (I wish we had the same clarity in C++). Given that standard-layout types are for compatibility with C and other languages, I do not see how P0137R1 could change that.
I would say P0137R1 is half-baked at this point, and we cannot use it to justify anything.
--Cheers,V.
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAA7YVg0uSONt%2BMOoa4p933S-0H_vAvZTro00_ZHnr4ugZrsV4A%40mail.gmail.com.
On Fri, Aug 26, 2016 at 1:42 PM, Viacheslav Usov <via....@gmail.com> wrote:On Sat, Aug 20, 2016 at 11:26 AM, D. B. <db0...@gmail.com> wrote:> I get all the points about different types of pointers, memory regions, etc. - which are valid counterarguments for totally arbitrary subtractions. But I don't don't grasp why such caveats should disallow (in fact: don't logically allow) arithmetic within the same (trivially copyable/standard layout) object.. Such an object must occupy a single, known region of storage. Just as is true for arrays.Let me try again.(0) [intro.memory]/1 "Every byte has a unique address."(1) [intro.object]/5 "An object of trivially copyable or standard-layout type (3.9) shall occupy contiguous bytes of storage."(2) [basic.compound]/3 "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."Not since P0137R1. But I'll assume that you're either working within C++14 as written (although no implementation conforms to that as written) or are inserting calls to std::launder where required.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/73160ad3-ee21-473d-8362-f39203217f7f%40isocpp.org.
On Fri, Aug 26, 2016 at 7:27 PM, Michał Dominiak <gri...@griwes.info> wrote:> The intent here is to describe what an object is, instead of giving a straight definition, because I honestly can't say how to word this in a way that'd give you a straight definition and not a description of what are the properties of an object.And, as I said, it fails to deliver on that intent. With the current "an object is a region of storage", we can tell that it is essentially the same definition as in C. We can infer that "type" is a just an interpretation, which can be changed without affecting other interpretations. With P0137R1, that is not clear. I do not think you said explicitly whether "type is just an interpretation, which can be changed without affecting other interpretations" is what you think we will have post-P0137R1, but it is evident just from this thread that there are people who think otherwise.> Also I don't believe there's any problem with the wording w.r.t. [expr.reinterpret.cast]/11. That paragraph keeps meaning the exact same thing as before. The only thing that changes is when the objects comes into existence and when it stops being an object.I hope that's what the authors of P0137R1 really have in mind. I hope that we will still have objects as merely a shorthand for "a region of storage", which we can interpret as any data type without having to "create" and "destroy" anything when the interpretation changes. Unfortunately, the message that P0137R1 sends to me (and other commenters) is different.
Some objects are polymorphic ([class.virtual]); the implementation generates information associated with each such object that makes it possible to determine that object's type during program execution. For other objects, the interpretation of the values found therein is determined by the type of the expressions (Clause [expr]) used to access them.
> I do not believe there's any problem with that. The C++ standard will specify how it works on the C++ sideIf the eventual outcome of P0137R1 will be compatible (for standard-layout types) with "object is a region of storage" and "type is just an interpretation, which can be changed without affecting other interpretations", then this is all moot. If not, then we will have a major problem.> Whether you consider it half-baked or not, it's a part of the working draft now, it's in the CD ballot, and then unless a NB comment removes it, it'll be in the next ballot and then in the international standard ISO 14882:2017 (...assuming we arrive on time...), so this is exactly the point where we start using it to explain how the language is going to work since C++17.Oh, I am pretty sure we can end up with a defective international standard. We have seen that happen many, many times.
--Cheers,V.
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAA7YVg0Y3On8HE05W9XS_UtJSL4YV6pMWtPYsooa5%3DALwM-48g%40mail.gmail.com.
On Sat, Aug 27, 2016 at 2:30 PM, Michał Dominiak <gri...@griwes.info> wrote:> Please help me understand your paragraph starting with "I hope" in the context of this quote ([intro.object]/1). Seriously, this and your statements seem not to ccompute.I do not see why. The language you quoted says, with more details and nuances, that "type is a just an interpretation, which can be changed without affecting other interpretations".> The way I'm reading this, P0137R1 doesn't change anything w.r.t. rules of interpreting the object types or their values, and essentially only redefines the lifetime of an object to start when it's created and end when it's destroyed, instead of being bound to the lifetime of its storage. I don't see any of the problems you're talking about after this change.We can have imaginary problems, too. Here is what Edward wrote: "Per [intro.object]/1 and the associated drafting note, an array of N unsigned char is *not* created when you obtain storage for an object type T, sizeof(T) == N." I then read the new language, as suggested, many times over, and it may be that I was trying just too hard. At some point I felt that, because in the new definition an object is not storage, some additional "create" event is required to reinterpret the storage. What would you say to this?
It may be that Edward says something that cannot really be said with the current or future standard terms, which is why we end up with those problems. Specifically, he says "array of N unsigned char is *not* created". But, indeed, it is not an array that is created, it is an object that is (or occupies) a region of storage. And being an array (or not) is still just an interpretation of that storage.
--Cheers,V.
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAA7YVg26Zc7%3DET-UdBnAMvWq%3DC851dDsBajOChDJ%3DxM8ZuFSAw%40mail.gmail.com.
> * Can this be made well-defined?
...
> So there might be some momentum already for specifically defining this.
> I hope so! (Otherwise I'll have to radically rethink my design, as this
> would be the only piece of formal UB in my current project)
FYI, there is CWG issue #1701.
"Array vs sequence in object representation"
http://wg21.cmeerw.net/cwg/issue1701
Probably, we need a formal proposal (which shows what should be portable
and why) to proceed the issue.
Let's wait from someone from CWG to confirm this. (I'm somewhat tempted to CC Richard to this thread...).
Maybe aliasing isn't the right word here, but I thought what your example does to form the pointer is already UB or implementation-defined at best... due to the cast from the weakest aligned char type to an object type that might be misaligned, and the fact the compiler has no evidence of there being a valid int at the adjusted position.
Whatever the many problems probably are, certainly it's horrid code and not something I would ever write! ;-) To be clear, I don't want the 'object as array of char' guarantee so that I can do offset acrobatics to other types... not at all. I'm not trying to define that given int a, b, c; then one should be able to say a[2] and get c.
All I want to do is calculate offsets between objects that are already unsigned char, in contiguous sequences, but which formally I can't because they weren't declared as an array of unsigned char.
On Wed, Aug 31, 2016 at 3:16 PM, 'Edward Catmur' via ISO C++ Standard - Future Proposals <std-pr...@isocpp.org> wrote:> Sorry, I can't find that analysis.
> I don't believe that you can "cast [the address of the complete object] to pointer to array of unsigned chars"Such a cast is essential to the analysis I referenced. Why do you not believe it is possible?
> C++ allows accessing an object as a sequence of unsigned char, but (apparently?) not an array of unsigned char. If that's true, then it follows that you can't use pointer arithmetic on an unsigned char * to obtain offsets into the object.No, that does not follow. We are not accessing the object at all.
> True; s/accessing/treating/. To clarify, I'm referring to the definition of adding or subtracting an integer from a pointer, 5.7/4 (emphasis mine): "[...] If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined." In other words, pointer arithmetic (with integer operands greater than 1) can only be done on array objects.[expr.reinterpret.cast]/11 "A glvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The result refers to the same object as the source glvalue, but with the specified type".