Ответ 1
Спасибо всем за ответ и комментирование, но я думаю, что правильный ответ - поскольку он стоит, код демонстрирует технический UB, хотя и исправляется. Я просмотрел некоторые из этих вопросов [ 1, 2 ] @xskxzr, и это привело меня к этой цитате из стандарта:
Если два объекта взаимно конвертируемы, то они имеют один и тот же адрес, и можно получить указатель на один из указателя на другой через reinterpret_cast. [Примечание. Объект массива и его первый элемент не являются взаимопереключателями, хотя они имеют одинаковый адрес. - конечная нота]
Затем на странице reinterpret_cast
есть следующая заметка с примером:
Предполагая, что требования к выравниванию выполнены, reinterpret_cast не изменяет значение указателя за пределами нескольких ограниченных случаев, связанных с объектами, конвертируемыми с указателем:
int arr[2];
int* p5 = reinterpret_cast<int*>(&arr); // value of p5 is unchanged by reinterpret_cast and
// is "pointer to arr"
Несмотря на то, что это компилируется без предупреждения и запускается, это технически UB, потому что p5
технически все еще является указателем на arr
а не arr[0]
. Поэтому в основном использование reinterpret_cast
как я его использовал, приводит к UB. Принимая во внимание вышеизложенное, если бы я должен был создать int *
непосредственно к первому int
(и это нормально в соответствии с ответом от @codekaizer), тогда это должно быть правильно, правильно?
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);
}
Но это, вероятно, и UB, так как указатель p
указывает на первый T
первого массива T
который имеет Y
элементов. Таким образом, p + X*Y
будет указывать на диапазон этого 1-го массива T
s, следовательно, UB (еще раз спасибо @xskxzr за ссылку и комментарий).
Если выражение P указывает на элемент x [i] объекта массива x с n элементами, выражения P + J и J + P (где J имеет значение j) указывают на (возможно-гипотетический) элемент x [i + j], если 0≤i + j≤n; в противном случае поведение не определено.
Итак, вот моя последняя попытка, прежде чем я сдаюсь:
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);
}
Здесь T arr[X][Y]
сначала преобразуется в T a[X*Y]
с снова reinterpret_cast
, который, как мне кажется, теперь действителен. Переосмыслил массив счастливо распадается на указатель на 1 - й элемент массива a
a[X*Y]
(a + X * Y
также находится в пределах диапазона) и преобразуется в итератор в std::sort
.
Версия TL; DR
Поведение в OP не определено из-за неправильного использования reinterpret_cast
. Правильный способ преобразования 2D-массива в 1D-массив:
//-- T arr2d[X][Y]
T(&arr1d)[X*Y] = reinterpret_cast<T(&)[X*Y]>(arr2d);
Выражение lval типа T1 может быть преобразовано в ссылку на другой тип T2. Результатом является значение lvalue или xvalue, относящееся к тому же объекту, что и исходное lvalue, но с другим типом. Временное создание не производится, копирование не производится, не создаются конструкторы или функции преобразования. Результирующая ссылка может быть доступна только безопасно, если это разрешено правилами псевдонимов типа
Всякий раз, когда делается попытка прочитать или изменить сохраненное значение объекта типа DynamicType с помощью glvalue типа AliasedType, поведение не определено, если только одно из следующего не выполняется:
- AliasedType и DynamicType аналогичны.
Неформально два типа аналогичны, если, игнорируя cv-квалификацию верхнего уровня
- они представляют собой массивы одинакового размера или оба массива неизвестной границы, а типы элементов массива аналогичны.
В заявлении
T
D
гдеD
имеет вид
D1 [ constant-expression opt ] attribute-specifier-seq opt
и тип идентификатора в объявлении
T
D1
является "производным-декларатором-типом-спискомT
", тогда тип идентификатораD
является типом массива; если тип идентификатораD
содержит автоматический тип-спецификатор, программа плохо сформирована.T
называется типом элемента массива;