Why do C and C++ support memberwise assignment of arrays within structs, but not generally?

Solution 1:

Here's my take on it:

The Development of the C Language offers some insight in the evolution of the array type in C:

  • http://cm.bell-labs.com/cm/cs/who/dmr/chist.html

I'll try to outline the array thing:

C's forerunners B and BCPL had no distinct array type, a declaration like:

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

would declare V to be a (untyped) pointer which is initialized to point to an unused region of 10 "words" of memory. B already used * for pointer dereferencing and had the [] short hand notation, *(V+i) meant V[i], just as in C/C++ today. However, V is not an array, it is still a pointer which has to point to some memory. This caused trouble when Dennis Ritchie tried to extend B with struct types. He wanted arrays to be part of the structs, like in C today:

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

But with the B,BCPL concept of arrays as pointers, this would have required the name field to contain a pointer which had to be initialized at runtime to a memory region of 14 bytes within the struct. The initialization/layout problem was eventually solved by giving arrays a special treatment: The compiler would track the location of arrays in structures, on the stack etc. without actually requiring the pointer to the data to materialize, except in expressions which involve the arrays. This treatment allowed almost all B code to still run and is the source of the "arrays convert to pointer if you look at them" rule. It is a compatiblity hack, which turned out to be very handy, because it allowed arrays of open size etc.

And here's my guess why array can't be assigned: Since arrays were pointers in B, you could simply write:

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

to rebase an "array". This was now meaningless, because the base of an array variable was not a lvalue anymore. So this assigment was disallowed, which helped to catch the few programs that did this rebasing on declared arrays. And then this notion stuck: As arrays were never designed to be first class citized of the C type system, they were mostly treated as special beasts which become pointer if you use them. And from a certain point of view (which ignores that C-arrays are a botched hack), disallowing array assignment still makes some sense: An open array or an array function parameter is treated as a pointer without size information. The compiler doesn't have the information to generate an array assignment for them and the pointer assignment was required for compatibility reasons. Introducing array assignment for the declared arrays would have introduced bugs though spurious assigments (is a=b a pointer assignment or an elementwise copy?) and other trouble (how do you pass an array by value?) without actually solving a problem - just make everything explicit with memcpy!

/* Example how array assignment void make things even weirder in C/C++, 
   if we don't want to break existing code.
   It's 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
}

This didn't change when a revision of C in 1978 added struct assignment ( http://cm.bell-labs.com/cm/cs/who/dmr/cchanges.pdf ). Even though records were distinct types in C, it was not possible to assign them in early K&R C. You had to copy them member-wise with memcpy and you could pass only pointers to them as function parameters. Assigment (and parameter passing) was now simply defined as the memcpy of the struct's raw memory and since this couldn't break exsisting code it was readily adpoted. As a unintended side effect, this implicitly introduced some kind of array assignment, but this happended somewhere inside a structure, so this couldn't really introduce problems with the way arrays were used.

Solution 2:

Concerning the assignment operators, the C++ standard says the following (C++03 §5.17/1):

There are several assignment operators... all require a modifiable lvalue as their left operand

An array is not a modifiable lvalue.

However, assignment to a class type object is defined specially (§5.17/4):

Assignment to objects of a class is defined by the copy assignment operator.

So, we look to see what the implicitly-declared copy assignment operator for a class does (§12.8/13):

The implicitly-defined copy assignment operator for class X performs memberwise assignment of its subobjects. ... Each subobject is assigned in the manner appropriate to its type:
...
-- if the subobject is an array, each element is assigned, in the manner appropriate to the element type
...

So, for a class type object, arrays are copied correctly. Note that if you provide a user-declared copy assignment operator, you cannot take advantage of this, and you'll have to copy the array element-by-element.


The reasoning is similar in C (C99 §6.5.16/2):

An assignment operator shall have a modifiable lvalue as its left operand.

And §6.3.2.1/1:

A modifiable lvalue is an lvalue that does not have array type... [other constraints follow]

In C, assignment is much simpler than in C++ (§6.5.16.1/2):

In simple assignment (=), the value of the right operand is converted to the type of the assignment expression and replaces the value stored in the object designated by the left operand.

For assignment of struct-type objects, the left and right operands must have the same type, so the value of the right operand is simply copied into the left operand.

Solution 3:

In this link: http://www2.research.att.com/~bs/bs_faq2.html there's a section on array assignment:

The two fundamental problems with arrays are that

  • an array doesn't know its own size
  • the name of an array converts to a pointer to its first element at the slightest provocation

And I think this is the fundamental difference between arrays and structs. An array variable is a low level data element with limited self knowledge. Fundamentally, its a chunk of memory and a way to index into it.

So, the compiler can't tell the difference between int a[10] and int b[20].

Structs, however, do not have the same ambiguity.