Может ли std:: array использовать фрагмент большего массива?

Предположим, что у нас есть указатель T* ptr; и ptr, ptr+1, … ptr+(n-1) все относятся к действительным объектам типа T.

Можно ли получить к ним доступ, как если бы они были STL array? Или имеет следующий код:

std::array<T,n>* ay = (std::array<T,n>*) ptr

вызывать поведение undefined?

Ответ 1

Да, его поведение Undefined, классическое...

Во-первых, поймите, что вы только что сделали:

std::array<T,n>* ay = (std::array<T,n>*) ptr

можно перевести как:

using Arr = std::array<T,n>;
std::array<T,n>* ay = reinterpret_cast<Arr*>( const_cast<TypeOfPtr>(ptr));

Вы не просто отбросили все, const и volatile квалификацию, но также набросили тип. См. Этот ответ: fooobar.com/questions/2175/...... бездисковое отбрасывание квалификаций cv также может привести к UB.

Во-вторых, поведение Undefined для доступа к объекту через указатель, который был отличен из несвязанного типа. См. строгое правило псевдонимов (Спасибо зениту). Поэтому любой доступ к чтению или записи через указатель ay равен undefined. Если вам очень повезло, код должен немедленно сработать. Если это сработает, злые дни ждут вас....

Обратите внимание, что std::array не является и никогда не будет таким же, как и все, что не является std::array.

Просто добавьте... В рабочий черновик стандарта С++ он перечисляет из явных правил преобразования. (вы можете прочитать их) и имеет пункт, в котором говорится, что

.....

5.4.3: Любое преобразование типа, не упомянутое ниже, и явно не определяемое пользователь ([class.conv]) плохо сформирован.

.....


Я предлагаю вам приготовить собственный array_view (надеюсь, он появится на С++ 17). Это очень легко. Или, если вы хотите получить какую-то собственность, вы можете приготовить простой такой:

template<typename T>
class OwnedArray{
    T* data_ = nullptr;
    std::size_t sz = 0;
    OwnedArray(T* ptr, std::size_t len) : data_(ptr), sz(len) {}
public:
    static OwnedArray own_from(T* ptr, std::size_t len)
    { return OwnedArray(ptr, len);  }

    OwnedArray(){}

    OwnedArray(OwnedArray&& o)
    { data_ = o.data_; sz = o.sz; o.data_=nullptr; o.sz=0; }

    OwnedArray& operator = (OwnedArray&& o)
    { delete[] data_; data_ = o.data_; sz = o.sz; o.data_=nullptr; o.sz=0; }

    OwnedArray(const OwnedArray& o) = delete;

    OwnedArray& operator = (const OwnedArray& o) = delete;    

    ~OwnedArray(){ delete[] data_; }

    std::size_t size() const { return sz; }

    T* data() return { data_; }

    T& operator[] (std::size_t idx) { return data_[idx]; }
};

... и вы можете развернуть больше функций-членов/const-квалификаций по своему усмотрению. Но это имеет оговорки... Указателю должно быть выделено сквозное new T[len]

Таким образом, вы можете использовать его в своем примере следующим образом:

auto ay = OwnedArray<decltype(*ptr)>::own_from(ptr, ptr_len);

Ответ 2

Да, это вызывает поведение undefined. Как правило, вы не можете указывать указатели на несвязанные типы между собой.

Код ничем не отличается от

std::string str;
std::array<double,10>* arr = (std::array<double,10>*)(&str);

Объяснение: Стандарт не обеспечивает никаких гарантий совместимости между std::array<T,n> и T*. Его просто нет. Он не говорит, что std::array является тривиальным типом. При отсутствии таких гарантий любое преобразование между T* и std::array<T,n> - это поведение undefined в том же масштабе, что и преобразование указателей в любые несвязанные типы.

Я также не понимаю, в чем преимущество доступа к уже построенному динамическому массиву как std::array.

P.S. Обычная оговорка. Cast, на его собственном, всегда на 100% отлично. Это косвенность приведенного указателя, который запускает фейерверк, но эта часть не указана для упрощения.

Ответ 3

Я отвечаю на первый вопрос здесь, поскольку второй уже рассмотрен в других ответах:

Recap: вы...

имеют указатель T* ptr; и ptr, ptr+1, … ptr+(n-1) все относятся к действительным объектам типа T.

И вы спрашиваете, есть ли это...

можно получить доступ к ним, как если бы они были STL array?

Ответ: Это не проблема, но она работает по-разному, как вы оценили в своем примере кода:

std::array<T*, N> arr;
for(int i = 0; i<N; ++i)
{
    arr[i] = ptr + i;
}

Теперь вы можете использовать элементы массива, как если бы они были оригинальными указателями. И нет никакого поведения undefined где-либо.

Ответ 4

Это вызывает поведение undefined. Вы используете reinterpret_cast для переноса между несвязанными типами, а правила на них довольно строгие. Для ближайшего предложения, предлагающего определенное поведение, рассмотрите следующий союз:

union {
    int carray[10];
    std::array<10, int> sarray;
}

Если два члена союза имеют один и тот же префикс (т.е. их первые члены k являются одним и тем же типом), вы можете написать объединению через один член и прочитать от другого, если вы ограничиваете себя членами в общий префикс. Если вы с уверенностью знаете, что std::array имеет массив C в качестве своего первого члена, то это определено. Кажется, что это "в основном" требуется (Является ли размер std:: array определенным стандартом).

Итак, в этом случае, если вы берете адрес carray, у вас есть int*, который можно условно отнести к std::array<10, int> косвенно через объединение.

Однако на самом деле нет причин для этого. Скорее всего, что-то вроде array_view. Это не в стандарте, но основная идея состоит в том, что его общий способ обернуть представление (не владение) любого массива. Базовая версия довольно просто реализовать самостоятельно или вы можете проверить это: https://github.com/rhysd/array_view.