Thank you all for replying and commenting, but I think the correct answer is - as it stands the code exhibits technical UB, though correctable. I have looked through some of those questions [1, 2] @xskxzr linked and it led me to this quote from the standard:
If two objects are pointer-interconvertible, then they have the same
address, and it is possible to obtain a pointer to one from a pointer
to the other via a reinterpret_-cast. [?Note: An array object and its
first element are not pointer-interconvertible, even though they have
the same address. —?end note?]
Then on reinterpret_cast
page there is the following note with an example:
Assuming that alignment requirements are met, a reinterpret_cast does
not change the value of a pointer outside of a few limited cases
dealing with pointer-interconvertible objects:
int arr[2];
int* p5 = reinterpret_cast<int*>(&arr); // value of p5 is unchanged by reinterpret_cast and
// is "pointer to arr"
Even though this compiles without warning and runs, this is technically a UB because p5
is technically still a pointer to arr
and not to arr[0]
. So basically the use of reinterpret_cast
the way I used it leads to UB. Taking the above into account, if I were to create int *
directly to the 1st int
(and this is ok according to the answer by @codekaizer), then this should be valid, right?:
template<typename T, size_t X, size_t Y>
void sort2(T(&arr)[X][Y])
{
T *p = &arr[0][0]; // or T *p = arr[0];
std::sort(p, p + X * Y);
}
But it probably is UB as well since the pointer p
is pointing to the first T
of the first array of T
s which has Y
elements. p + X*Y
therefore will be pointing out of range of this 1st array of T
s, hence UB (thanks again to @xskxzr for the link and comment).
If the expression P points to element x[i] of an array object x with n
elements, the expressions P + J and J + P (where J has the value j)
point to the (possibly-hypothetical) element x[i+j] if 0≤i+j≤n;
otherwise, the behavior is undefined.
So here is my final attempt before I give up:
template<typename T, size_t X, size_t Y>
void sort2(T(&arr)[X][Y])
{
T(&a)[X * Y] = reinterpret_cast<T(&)[X * Y]>(arr);
std::sort(a, a + X * Y);
}
Here T arr[X][Y]
is first converted to T a[X*Y]
with, again, reinterpret_cast
, which I think is now valid. The reinterpreted array a
happily decays to a pointer to 1st element of array a[X*Y]
(a + X * Y
is also within the range) and gets converted to an iterator in std::sort
.
TL;DR version
Behaviour in the OP is Undefined because of improper use of reinterpret_cast
. The correct way to convert 2D array to 1D array would be:
//-- T arr2d[X][Y]
T(&arr1d)[X*Y] = reinterpret_cast<T(&)[X*Y]>(arr2d);
An lvalue expression of type T1 can be converted to reference to
another type T2. The result is an lvalue or xvalue referring to the
same object as the original lvalue, but with a different type. No
temporary is created, no copy is made, no constructors or conversion
functions are called. The resulting reference can only be accessed
safely if allowed by the type aliasing rules
Aliasing rules:
Whenever an attempt is made to read or modify the stored value of an
object of type DynamicType through a glvalue of type AliasedType, the
behavior is undefined unless one of the following is true:
- AliasedType and DynamicType are similar.
Type similarity:
Informally, two types are similar if, ignoring top-level
cv-qualification
- they are both arrays of the same size or both arrays of unknown bound, and the array element types are similar.
Array element type:
In a declaration T
D
where D
has the form
D1 [ constant-expression opt ] attribute-specifier-seq opt
and the type of the identifier in the declaration T
D1
is
“derived-declarator-type-list T
”, then the type of the identifier of D
is an array type; if the type of the identifier of D
contains the auto type-specifier, the program is ill-formed. T
is called the array element type;