Обработка 2D-массива в виде массива 1D

Скажем, у нас есть 2D-массив int:

int a[3][4] = { { 1,3,2,4 }, { 2,1,5,3 }, { 0,8,2,3 } };

Является ли он законным и действительным, чтобы принять его адрес и переосмыслить его как указатель на 1D массив int s? В принципе:

int *p = reinterpret_cast<int *>(&a);

Чтобы я мог делать что-то вроде (грубо):

template<typename T, size_t X, size_t Y>
void sort2(T(&arr)[X][Y])
{
    T *p = reinterpret_cast<T *>(&arr);
    std::sort(p, p + X*Y);
}

ДЕМО: https://ideone.com/tlm190

Насколько я знаю, стандарт гарантирует, что выравнивание 2D-массива будет смежным в памяти, и хотя p + X*Y технически выходит за пределы диапазона, никогда не открывается, поэтому не должно приводить к неопределенному поведению.

Могу ли я полностью обрабатывать 2D-массивы как 1D-массивы, когда это необходимо?

Ответы

Ответ 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 называется типом элемента массива;

Ответ 2

С http://www.cplusplus.com/doc/tutorial/arrays/

int jimmy [3][5];   // is equivalent to
int jimmy [15];     // (3 * 5 = 15)  

при создании массива (любого измерения) память массива является фиксированным блоком памяти в size = sizeof(type) * dim0 * dim1 *....;

Итак, на ваш вопрос, да, вы можете безопасно переделать массив в один размерный массив.

Ответ 3

Да. Это законно и справедливо.

Согласно dcl.array:

Если E является массивом n -dimensional ранга я × j × ⋯ × k, то E, появляющееся в выражении, которое подчиняется преобразованию от массива к указателю, преобразуется в указатель на (n-1) [CN00 ] массив с рангом j × ⋯ × k. Если к этому указателю применяется оператор *, явно или неявно в результате подписи, результатом является массив с указателем (n-1) -dimensional, который сам сразу преобразуется в указатель.