Короткий ответ — обычно нет, это небезопасно, если только вы точно не уверены в том, что в памяти действительно существует объект массива такого точного размера и типа. reinterpret_cast сам по себе просто меняет «интерпретацию» адреса, но не создаёт объект и не гарантирует соблюдение правил выравнивания/аляcсинга/времени жизни объектов. Поэтому последующее разыменование такого указателя часто приводит к неопределённому поведению.
Развёрнутые моменты, которые важно понимать:
- Существование объекта. Приведение T* к T (*)[N] и разыменование (получение ссылки/массивного lvalue) законно только если по этому адресу действительно расположен объект массива типа T[N] (или объект, pointer-interconvertible с таким массивом). Если по адресу лежит одиночный объект T, буфер, выделенный malloc, или N != реальный размер массива — это UB.
- Время жизни и инициализация. Если память была выделена как "сырая" (malloc, void*, uninitialized buffer) и в неё просто записаны биты, то для типов с неклассическим поведением (не trivially copyable) объекты могут не быть созданными. Даже для trivially copyable типов есть тонкости в стандарте C++ (object model, implicit creation). Практически надёжно только когда вы реально создали массив (например new T[N] или статический/автоматический T a[N]).
- Выравнивание. Массив выровнен как его элемент, но если исходный указатель не выровнен под T, разыменование будет UB.
- Strict aliasing. Если вы приводите к типу массива с тем же T — элементы имеют тот же тип, и обращение к элементам не нарушает правила алиасинга. Но если вы меняете элементный тип — вы получите UB при доступе (исключение: доступ через char* и некоторые другие случаи).
- Реализация. reinterpret_cast между указателями к объектам обычно просто переписывает битовый образ указателя, но стандарт оставляет детали реализации. На реальных платформах обычно одинаковые адреса, но нельзя полагаться на это для корректности программы по стандарту.
Примеры
- Безопасно:
- int *p = new int[10];
auto pa = reinterpret_cast<int(*)[10]>(p);
(*pa)[3] = 7; // допустимо: p действительно указывает на массив int[10]
- Небезопасно:
- int *p = new int;
auto pa = reinterpret_cast<int(*)[10]>(p);
(*pa)[0] = 1; // UB: по адресу нет массива int[10]
- void *buf = malloc(10*sizeof(int));
auto pa = reinterpret_cast<int(*)[10]>(buf);
(*pa)[0] = 1; // UB, если вы не создали объекты int в буфере
Рекомендации
- Если вам нужен «вид на N элементов», лучше использовать std::array<T,N> (если объект реально массив), std::span<T> или просто передавать T* + размер.
- Если вы работаете с выделением и инициализацией в сырую память, предпочитайте placement new для создания массива/объектов, чтобы соблюдалось правило времени жизни.
- Если вы по каким‑то причинам всё же делаете reinterpret_cast для низкоуровневой работы, тщательно документируйте и гарантируйте: выравнивание, время жизни, реальный размер массива и тип элементов.
Если хотите, можете привести ваш конкретный пример кода (тот шаблон, который вы начали) — посмотрю и скажу, безопасно ли в вашем случае.