Совместим ли бит std::array со старым массивом C?

Одинаково ли базовое битовое представление для std::array<T,N> v и T u[N]?

Другими словами, безопасно ли копировать N*sizeof(T) байта из одного в другой? (Либо через reinterpret_cast, либо через memcpy.)

Изменить:

Для пояснения, акцент делается на одном представлении битов и reinterpret_cast.

Например, предположим, что у меня есть эти два класса для некоторого тривиально копируемого типа T для некоторого N:

struct VecNew {
    std::array<T,N> v;
};

struct VecOld {
    T v[N];
};

И есть устаревшая функция

T foo(const VecOld& x);

Если представления совпадают, то этот вызов безопасен и позволяет избежать копирования:

VecNew x;
foo(reinterpret_cast<const VecOld&>(x));

person shinjin    schedule 07.09.2016    source источник
comment
Вы делаете копию, используя data/&array_name[0] или используя имя самого массива?   -  person NathanOliver    schedule 07.09.2016
comment
Не через reinterpret_cast, из-за строгого алиасинга.   -  person Dan    schedule 07.09.2016
comment
Хм... первоначальный вопрос был о копировании, новый вопрос о reinterpret_cast-инге. Это несколько другое...   -  person Barry    schedule 08.09.2016
comment
@Barry, первоначальный вопрос был совместим ли бит std::array со старым массивом C? И все еще совместим. Все остальное - следствие этого.   -  person shinjin    schedule 08.09.2016
comment
Похоже, вы пытаетесь модернизировать устаревший код C++, заменяя старые конструкции новыми, верно?   -  person Laurent LA RIZZA    schedule 08.09.2016
comment
Затем кто-то изменяет VecNew, например, добавляя новое поле, и наслаждайтесь отладкой. Спасибо, не надо.   -  person Slava    schedule 08.09.2016


Ответы (5)


Я говорю да (но стандарт этого не гарантирует).

Согласно [массиву]/2:

Массив представляет собой агрегат ([dcl.init.aggr]), который может быть инициализирован списком до N элементов, типы которых могут быть преобразованы в T.

И [dcl.init.aggr]:

агрегат – это массив или класс (предложение [класс]) с

  • нет пользовательских, явных или унаследованных конструкторов ([class.ctor]),

  • нет частных или защищенных нестатических элементов данных (пункт [class.access]),

  • нет виртуальных функций ([class.virtual]) и

  • нет виртуальных, частных или защищенных базовых классов ([class.mi]).

В свете этого "может быть инициализирован списком" возможен только в том случае, если в начале класса нет других членов и нет виртуальной таблицы.

Затем data() указывается как:

constexpr T* data() noexcept;

Возвращает: указатель, в котором [data(), data() + size()) является допустимым диапазоном, а data() == addressof(front()).

Стандарт в основном хочет сказать «он возвращает массив», но оставляет дверь открытой для других реализаций.

Единственная возможная другая реализация — это структура с отдельными элементами, и в этом случае вы можете столкнуться с проблемами алиасинга. Но, на мой взгляд, такой подход не добавляет ничего, кроме сложности. Нет ничего полезного в том, чтобы развернуть массив в структуру.

Таким образом, нет нет смысла не реализовывать std::array как массив.

Но лазейка есть.

person rustyx    schedule 07.09.2016
comment
Я не согласен с тем, что могут возникнуть проблемы с псевдонимом. Каковы ваши доводы в пользу этого утверждения? - person Brian Bi; 08.09.2016
comment
Структура и массив являются несовместимыми типами с точки зрения строгого алиасинга. - person rustyx; 08.09.2016
comment
Я не думаю, что ваша интерпретация строгого правила псевдонимов верна. Если бы это было так, то тип массива также был бы несовместим с типом его элемента, что явно абсурдно. - person Brian Bi; 08.09.2016
comment
Его утверждение о строгом псевдониме не подразумевает того, что вы утверждаете. - person alexchandel; 08.09.2016
comment
@ Брайан Это не то, о чем говорил RustyX. Массив никогда не был совместим с struct, имеющим такое же количество однотипных членов. Однако даже ваш косвенный вывод о совместимости указателей на массивы и указателей на их элементы скоро окажется слишком верным! См. ответ ecatmur о веселье в запасе из находящегося в разработке P0137R1. И, пожалуйста, если вы так склонны и в состоянии, подать комментарий Национального органа, выражающий скептицизм по этому поводу. - person underscore_d; 08.09.2016

Это не дает прямого ответа на ваш вопрос, но вы должны просто использовать std::copy:

T c[N];
std::array<T, N> cpp;

// from C to C++
std::copy(std::begin(c), std::end(c), std::begin(cpp));

// from C++ to C
std::copy(std::begin(cpp), std::end(cpp), std::begin(c));

Если T является тривиально копируемым типом, он скомпилируется до memcpy. Если это не так, то это будет выполнять поэлементное копирование и будет правильным. В любом случае, это правильно и вполне читабельно. Нет необходимости в ручной байтовой арифметике.

person Barry    schedule 07.09.2016
comment
придирка: std::copy не всегда компилируется в memcpy это деталь реализации. Например, VC++ использует memmove для байтовых копий. - person Mgetz; 07.09.2016
comment
Я разрываюсь. Это отличный ответ ... на другой вопрос! - person underscore_d; 08.09.2016
comment
godbolt.org/g/SGdWwp Похоже, он выполняет быстрое копирование, только если два аргумента тот же тип массива (только test и test3 компилируются в memmove). - person Bad_ptr; 21.07.2018

std::array предоставляет метод data(), который можно использовать для копирования в/из массива c-стиля правильный размер:

const size_t size = 123;
int carray[size];
std::array<int,size> array;

memcpy( carray, array.data(), sizeof(int) * size );
memcpy( array.data(), carray, sizeof(int) * size );

Как указано в документации.

Этот контейнер представляет собой агрегатный тип с той же семантикой, что и структура, содержащая массив C-стиля T[N] в качестве единственного нестатического члена данных.

так что кажется, что объем памяти будет совместим с массивом в стиле c, хотя неясно, почему вы хотите использовать «хаки» с reinterpret_cast, когда есть правильный способ, который не требует каких-либо накладных расходов.

person Slava    schedule 07.09.2016
comment
Это как раз та часть, которую я хотел бы уточнить. - person shinjin; 07.09.2016
comment
ты не ответил почему - person Slava; 07.09.2016

Требование к методу data() состоит в том, чтобы он возвращал указатель T* таким образом, что:

[data(), data() + size()) — допустимый диапазон, а data() == addressof(front()).

Это означает, что вы можете получить доступ к каждому элементу последовательно с помощью указателя data(), и поэтому, если T тривиально копируется, вы действительно можете использовать memcpy для копирования sizeof(T) * size() байтов в/из массива T[size()], поскольку это эквивалентно memcpyобработке каждого элемента по отдельности.

Однако вы не можете использовать reinterpret_cast, поскольку это нарушило бы строгое сглаживание, так как data() не требуется фактически поддерживаться массивом, а также, даже если вы должны были гарантировать, что std::array содержит массив, поскольку С++ 17 вы не можете (даже используя reinterpret_cast) привести указатель на массив к/от указателя к его первому элементу (вы должны использовать std::launder).

person ecatmur    schedule 07.09.2016
comment
поскольку data() не требуется на самом деле поддерживаться массивом, хм, это чепуха. - person Cheers and hth. - Alf; 07.09.2016
comment
Что касается C++17, вы не можете (даже с помощью reinterpret_cast) привести указатель к массиву к/от указателя к его первому элементу (вы должны использовать std::launder), это звучит интересно: комитет сходит с ума! Больше информации, пожалуйста. Тем временем я приготовлю попкорн. - person Cheers and hth. - Alf; 07.09.2016
comment
@Cheersandhth.-Alf не обязательно должен поддерживаться массивом - тривиально верно для N = 0 и N = 1; реализуется через сотрудничество между вендором и библиотекой для N›1. - person ecatmur; 07.09.2016
comment
@Cheersandhth.-Alf указатель на массив нельзя преобразовать в/из указателя на его первый элемент: см. open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0137r1.html - person ecatmur; 07.09.2016
comment
Не N=1; переменная представляет собой массив размера 1. - person Cheers and hth. - Alf; 07.09.2016
comment
@Cheersandhth.-Alf для арифметики и сравнения указателей; не для всех целей и уж точно не в том, что касается алиасинга. - person ecatmur; 07.09.2016
comment
Хм, по поводу ссылки, это стена текста. Несколько сотен километров. Можете ли вы как-то расплывчато указать область, скажем, менее 203 метров? - person Cheers and hth. - Alf; 07.09.2016
comment
re конечно нет, это ерунда. - person Cheers and hth. - Alf; 07.09.2016
comment
Похоже, речь идет о предоставлении поставщику компилятора контрольного пакета акций в управлении стандартом, откуда недостатки и глупое поведение этого компилятора становятся стандартизированными. Ну что ж. - person Cheers and hth. - Alf; 07.09.2016
comment
@Cheersandhth.-Альфа изменения и дополнения к [basic.compound]/3 - это действительно забавно. Обратите внимание, что я изначально связал неправильную версию; вы должны читать P0137r1. - person ecatmur; 07.09.2016
comment
@Cheersandhth.-Возможно, Альф ерунда, но это направление, в котором движется Стандарт: int a; int (&r)[1] = reinterpret_cast<int(&)[1]>(a); r[0] = 42; теперь UB. Воистину мы живем в интересное время. - person ecatmur; 07.09.2016
comment
Вы не знаете, дали ли люди, выполняющие эту работу, какое-либо обоснование изменений? Например, чего они надеются достичь? - person Cheers and hth. - Alf; 07.09.2016
comment
@Cheersandhth.-Alf, насколько я понимаю, они формализуют то, что компиляторы уже делают, позволяя реализовать контейнеры, которые создают и уничтожают объекты (особенно векторные и необязательные). Предположительно, цель состоит в том, чтобы выжать все до последней капли UB из правил псевдонимов, чтобы лучше работать со SPEC (кхм: с пользовательским кодом). - person ecatmur; 07.09.2016
comment
С какой стати авторы, сразу после того, как специально разрешили преобразование между указателями на структуру стандартного макета и ее 1-го члена, а затем добавили - в чертовом примечании, как будто мимоходом запоздало - что такое же пособие не распространяется на массив и его 1-й элемент? Есть ли нюанс, который я упускаю, где одно должно быть разрешено, а второе нет? Неужели ( (NonexistentStruct*)somePointer )->nonexistentMember не менее опасен, чем ( (NonexistentArray[42]*)somePointer )[23]? ... или любой другой синтаксис; Я не занимаюсь этим, офк. Какое важное различие я упускаю? - person underscore_d; 08.09.2016
comment
Бьюсь об заклад, многие хаки libboost сейчас сломаются. - person paulotorrens; 08.09.2016
comment
@underscore_d дело не в опасности, а в оптимизации; большая часть научного кода (кашель SPEC кашель) может быть эффективно ускорена, если компилятор предполагает, что массивы и указатели разного размера не имеют псевдонима, даже когда тип элемента такой же. Ускорение, которое это дает, считается (автором компилятора и, честно говоря, их клиентами, пишущими научный код в стиле Fortran), стоящим потенциальной путаницы и поломки для их пользователей, пишущих больше системного или объектно-ориентированного кода. - person ecatmur; 08.09.2016
comment
@ecatmur Просто optional. vector все еще формально не работает. В частности, vector::data невозможно реализовать в стандартном C++. - person T.C.; 09.09.2016
comment
@Т.С. Можно ссылку на обсуждение проблемы с vector? - person underscore_d; 10.09.2016

array мало что говорит о базовом типе, для которого вы его создаете.

Чтобы иметь любую возможность получения полезных результатов от использования memcpy или reinterpret_cast для копирования, тип, для которого вы создали его экземпляр, должен быть тривиально копируемым. Хранение этих элементов в array не влияет на требование, чтобы memcpy (и подобные) работали только с тривиально копируемыми типами.

array должен быть непрерывным контейнером и агрегатом, что в значительной степени означает, что хранилищем для элементов должен быть массив. Стандарт показывает это так:

T elems[N]; // exposition only

Однако позже в нем есть примечание, которое, по крайней мере, подразумевает, что требуется массив (§[array.overview]/4):

[Примечание. Переменная-член elems показана только для демонстрации, чтобы подчеркнуть, что array является агрегатом класса. Имя elems не является частью интерфейса массива. — конец примечания]

[курсив добавлен]

Обратите внимание, что на самом деле не требуется только конкретное имя elems.

person Jerry Coffin    schedule 07.09.2016
comment
новый черновик избавился от этой части. Теперь у нас есть только то, что это агрегат, который может быть инициализирован списком с N Ts (но +1). - person Barry; 07.09.2016
comment
@Barry: я совсем не уверен, что это действительно сильно меняет. На первый взгляд, я не вижу способа удовлетворить его требования (непрерывный контейнер, совокупность), кроме как иметь только один элемент данных, который является массивом. Я предполагаю, что если бы вы могли гарантировать отсутствие заполнения между элементами, вы могли бы создать вариативный шаблон дискретных элементов, но только потому, что элементы по-прежнему будут адресуемыми, как массив. - person Jerry Coffin; 07.09.2016
comment
Инициализация не могла бы работать, если бы array не была простой struct оболочкой необработанного массива. - person Cheers and hth. - Alf; 07.09.2016
comment
@JerryCoffin О, я не говорю, что std::array не является оболочкой для необработанного массива. Я просто говорю, что теперь формулировка вокруг этого описания совершенно другая (не знаю, каково значение этого шанса, просто указываю на это). - person Barry; 07.09.2016
comment
Инициализация (но не другие части) могла бы работать, если бы хранилище было дискретными элементами в правильном порядке. - person Jerry Coffin; 07.09.2016
comment
Я имею в виду, что функциональность data гарантирует только то, что там действительно есть массив. Инициализация означает, что перед этим массивом ничего нет. Итак, есть массив, и перед ним ничего нет, поэтому указатель на него как на первый элемент структуры может быть переинтерпретирован как указатель на структуру и наоборот (это в конце раздела о членах класса). - person Cheers and hth. - Alf; 08.09.2016
comment
@JerryCoffin (но не другие части), да, например, базовая индексация и арифметика указателей! - person underscore_d; 08.09.2016