Почему C и C++ поддерживают членское присвоение массивов внутри структур, но не в целом?

Я понимаю, что членское назначение массивов не поддерживается, поэтому следующее не будет работать:

int num1[3] = {1,2,3};
int num2[3];
num2 = num1; // "error: invalid array assignment"

Я просто принял это как факт, полагая, что цель языка - предоставить открытую среду и позволить пользователю решить, как реализовать что-то, например, копирование массива.

Тем не менее, следующее работает:

struct myStruct { int num[3]; };
struct myStruct struct1 = {{1,2,3}};
struct myStruct struct2;
struct2 = struct1;

Массив num[3] struct1 элемента присваивается из его экземпляра в struct1, в его экземпляр в struct2.

Почему для массивов поддерживается присвоение массивов по элементам, а не вообще?

редактировать: Роджер Пейт комментарий в ветке std :: string в структуре - Проблемы с копированием/присваиванием? кажется, указывает на общее направление ответа, но я не знаю достаточно, чтобы подтвердить это сам.

редактировать 2: много отличных ответов. Я выбираю Лютера Блиссетта, потому что меня больше всего интересует философское или историческое обоснование поведения, но ссылка Джеймса МакНеллиса на сопутствующую документацию спецификации также была полезна.

Ответ 1

Вот мой пример:

Разработка языка C предлагает некоторое представление об эволюции типа массива в C:

Я попытаюсь описать массив:

C forerunners B и BCPL не имели определенного типа массива, объявление вроде:

auto V[10] (B)
or 
let V = vec 10 (BCPL)

объявит, что V является (нетипизированным) указателем, который инициализируется, чтобы указывать на неиспользуемую область из 10 "слов" памяти. B уже использовал * для разыменования указателя и имел короткую нотацию [], *(V+i) означал V[i], как и в C/С++ сегодня. Однако V не является массивом, он по-прежнему является указателем, который должен указывать на некоторую память. Это вызвало проблемы, когда Деннис Ритчи пытался расширить B с помощью типов структур. Он хотел, чтобы массивы были частью структур, например, сегодня в C:

struct {
    int inumber;
    char name[14];
};

Но с понятием B, BCPL в массивах в качестве указателей это потребовало бы, чтобы поле name содержало указатель, который должен был инициализироваться во время выполнения, до области памяти в 14 байтов внутри структуры. В конечном итоге проблема инициализации/компоновки была решена путем предоставления массивов специальной обработки: компилятор отслеживал местоположение массивов в структурах, в стеке и т.д., Не требуя при этом указания на то, чтобы данные материализовались, за исключением выражений, которые связаны с массивами. Это обращение позволило почти всем B-кодам продолжать работать и является источником правила "преобразование массивов в указатель, если вы смотрите на них". Это взлом совместимости, который оказался очень удобным, потому что он допускал массивы открытого размера и т.д.

И вот мое предположение, почему массив не может быть назначен: поскольку массивы были указателями в B, вы могли просто написать:

auto V[10];
V=V+5;

чтобы переустановить "массив". Это было бессмысленно, потому что база переменной массива больше не была lvalue. Таким образом, это ограничение было отменено, что помогло поймать несколько программ, которые делали это сбрасыванием объявленных массивов. И тогда это понятие застряло: поскольку массивы никогда не были предназначены для первого класса, цитируемого системой типа C, они в основном рассматривались как особые звери, которые становятся указателями, если вы их используете. И с определенной точки зрения (которая игнорирует, что C-массивы - неудачный взлом), запрещение назначения массива все еще имеет смысл: открытый массив или параметр функции массива обрабатываются как указатель без информации о размере. У компилятора нет информации для создания для них назначения массива, и назначение указателя было необходимо по соображениям совместимости. Представление назначения массива для объявленных массивов привело бы к ошибкам, если ложные привязки (есть назначение a = ba-указателя или элементарная копия?) И другие проблемы (как вы передаете массив по значению?) Без фактического решения проблемы - просто сделайте все явно с memcpy!

/* Example how array assignment void make things even weirder in C/C++, 
   if we don't want to break existing code.
   It actually better to leave things as they are...
*/
typedef int vec[3];

void f(vec a, vec b) 
{
    vec x,y; 
    a=b; // pointer assignment
    x=y; // NEW! element-wise assignment
    a=x; // pointer assignment
    x=a; // NEW! element-wise assignment
}

Это не изменилось, когда в версии C в 1978 добавлено назначение структуры (http://cm.bell-labs.com/cm/cs/who/dmr/cchanges.pdf). Несмотря на то, что записи были отличными типами на C, их невозможно было назначить в раннем K & R C. Вам пришлось скопировать их с помощью memcpy по-члену, и вы могли бы передавать только указатели на них в качестве функциональных параметров. Присвоение (и передача параметров) теперь просто было определено как memcpy структурной необработанной памяти, и поскольку это не могло нарушить существующий код, он был легко объявлен. Как непреднамеренный побочный эффект, это неявно вводило какое-то назначение массива, но это происходило где-то внутри структуры, поэтому это не могло действительно вызвать проблемы с использованием массивов.

Ответ 2

Что касается операторов присваивания, то в стандарте С++ указано следующее (С++ 03 §5.17/1):

Существует несколько операторов присваивания... все требуют модификации lvalue в качестве их левого операнда

Массив не является изменяемым lvalue.

Однако назначение объекта типа класса определяется специально (§5.17/4):

Назначение объектам класса определяется оператором присваивания копии.

Итак, мы посмотрим, что делает неявно объявленный оператор назначения копирования для класса (§12.8/13):

Неявно определенный оператор присваивания экземпляра для класса X выполняет поэтапное присвоение его подобъектов.... Каждый подобъект присваивается в соответствии с его типом:
...
- если подобъектом является массив, каждый элемент присваивается в соответствии с типом элемента...

Итак, для объекта типа класса массивы копируются правильно. Обратите внимание, что если вы предоставляете пользовательский оператор присваивания копий, вы не можете воспользоваться этим, и вам придется копировать массив по элементам.


Обоснование аналогично в C (C99 §6.5.16/2):

Оператор присваивания должен иметь модифицируемое lvalue в качестве его левого операнда.

И §6.3.2.1/1:

Модифицированное значение lvalue - это lvalue, который не имеет типа массива... [другие ограничения следуют]

В C назначение намного проще, чем в С++ (§6.5.16.1/2):

В простом присваивании (=) значение правильного операнда преобразуется в тип присваивание и заменяет значение, хранящееся в объекте, указанном слева операнд.

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

Ответ 3

В этой ссылке: http://www2.research.att.com/~bs/bs_faq2.html есть раздел о назначении массива:

Две основные проблемы с массивами заключаются в том, что

  • массив не знает свой собственный размер
  • имя массива преобразуется в указатель на его первый элемент при малейшей провокации

И я думаю, что это фундаментальное различие между массивами и структурами. Переменная массива - это элемент данных низкого уровня с ограниченными знаниями. По сути, это кусок памяти и способ индексирования в нее.

Итак, компилятор не может определить разницу между int a [10] и int b [20].

Структуры, однако, не имеют такой же двусмысленности.

Ответ 4

Я знаю, все, кто отвечал, являются экспертами на C/С++. Но я подумал, что это основная причина.

num2 = num1;

Здесь вы пытаетесь изменить базовый адрес массива, что недопустимо.

и, конечно, struct2 = struct1;

Здесь объект struct1 присваивается другому объекту.

Ответ 5

Другая причина, по которой не было предпринято никаких дополнительных усилий для расширения массивов в C, заключается в том, что присвоение массивов было бы не столь полезным. Хотя это может быть легко достигнуто в C, оборачивая его в структуру (а адрес структуры может быть просто приведен к адресу массива или даже адресу первого элемента массива для дальнейшей обработки), эта функция используется редко. Одна из причин заключается в том, что массивы разных размеров несовместимы, что ограничивает преимущества присваивания или, соответственно, передачи функций по значению.

Большинство функций с параметрами массива в языках, где массивы являются первоклассными типами, написаны для массивов произвольного размера. Затем функция обычно перебирает заданное количество элементов, информацию, которую предоставляет массив. (В Си, конечно, идиома состоит в том, чтобы передавать указатель и отдельное количество элементов.) Функция, которая принимает массив только одного определенного размера, не требуется так часто, так что не так много пропускается. (Это меняется, когда вы можете оставить его компилятору для генерации отдельной функции для любого размера массива, как в случае шаблонов C++; именно поэтому полезен std::array.)