Por que o C ++ suporta a atribuição de matriz de matrizes dentro de estruturas, mas geralmente não?

Eu entendo que a atribuição de matrizes de membros não é suportada, de modo que o seguinte não funcionará:

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

Acabei de aceitar isso como um fato, imaginando que o objective da linguagem é fornecer uma estrutura aberta e permitir que o usuário decida como implementar algo como a cópia de uma matriz.

No entanto, o seguinte funciona:

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

A matriz num[3] é associada a membros de sua instância em struct1 , em sua instância em struct2 .

Por que a atribuição de arrays por membros é suportada por structs, mas não em geral?

edit : O comentário de Roger Pate no tópico std :: string in struct – Problemas de cópia / atribuição? parece apontar na direção geral da resposta, mas não sei o suficiente para confirmar isso.

edição 2 : Muitas respostas excelentes. Eu escolhi o de Luther Blissett porque eu estava pensando principalmente sobre a lógica filosófica ou histórica por trás do comportamento, mas a referência de James McNellis à documentação de especificações relacionada também foi útil.

Aqui está minha opinião sobre isso:

O desenvolvimento da linguagem C oferece algumas dicas sobre a evolução do tipo de matriz em C:

Vou tentar delinear a coisa da matriz:

Os precursores de C, B e BCPL, não possuíam nenhum tipo de array distinto, uma declaração como:

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

declararia V como sendo um ponteiro (não typescript) que é inicializado para apontar para uma região não utilizada de 10 “palavras” de memory. B já usava * para desreferenciamento de ponteiro e tinha a notação [] mão curta, *(V+i) significava V[i] , assim como em C / C ++ hoje. No entanto, V não é uma matriz, ainda é um ponteiro que tem que apontar para alguma memory. Isso causou problemas quando Dennis Ritchie tentou estender B com tipos de struct. Ele queria matrizes para fazer parte das estruturas, como em C hoje:

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

Mas com o conceito B, BCPL de arrays como pointers, isso exigiria que o campo name contivesse um ponteiro que tinha que ser inicializado em tempo de execução para uma região de memory de 14 bytes dentro da estrutura. O problema de boot / layout foi eventualmente resolvido dando às matrizes um tratamento especial: O compilador rastrearia a localização das matrizes em estruturas, na pilha, etc., sem precisar realmente do ponteiro para os dados se materializarem, exceto em expressões que envolvem as matrizes. Esse tratamento permitiu que quase todo o código B ainda fosse executado e é a origem da regra “matrizes convertidas para ponteiro se você olhar para elas” . É um truque de compatibilidade, que acabou por ser muito útil, porque permitiu matrizes de tamanho aberto, etc.

E aqui está o meu palpite por que o array não pode ser atribuído: como os arrays eram pointers em B, você poderia simplesmente escrever:

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

para rebase de um “array”. Isso agora não fazia sentido, porque a base de uma variável de matriz não era mais um lvalue. Portanto, essa atribuição não foi permitida, o que ajudou a capturar os poucos programas que fizeram isso com os arrays declarados . E então essa noção permaneceu: Como as matrizes nunca foram projetadas para serem citadas de primeira class do sistema do tipo C, elas eram tratadas principalmente como bestas especiais que se tornariam ponteiro se você as usasse. E a partir de um certo ponto de vista (que ignora que matrizes C são um hack corrompido), não permitir a atribuição de matrizes ainda faz algum sentido: Um array aberto ou um parâmetro de function array é tratado como um ponteiro sem informações de tamanho. O compilador não tem as informações para gerar uma atribuição de matriz para eles e a atribuição de ponteiro foi necessária por motivos de compatibilidade. Introduzir a atribuição de matriz para as matrizes declaradas teria introduzido bugs através de falsas atribuições (é uma atribuição de ponteiro ou uma cópia elementar?) E outros problemas (como você passa uma matriz por valor?) Sem realmente resolver um problema – basta fazer tudo explícito com 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 } 

Isso não mudou quando uma revisão de C em 1978 adicionou a atribuição de estrutura ( http://cm.bell-labs.com/cm/cs/who/dmr/cchanges.pdf ). Mesmo que os registros fossem tipos distintos em C, não era possível atribuí-los no início do K & R C. Você tinha que copiá-los com o membro memcpy e você poderia passar apenas pointers para eles como parâmetros de function. Assigment (e passagem de parâmetros) agora era simplesmente definido como o memcpy da memory bruta da estrutura e, como isso não podia quebrar o código exsistente, era prontamente adivinhado. Como um efeito colateral não intencional, isso implicitamente introduzia algum tipo de atribuição de matriz, mas isso acontecia em algum lugar dentro de uma estrutura, então isso não poderia realmente introduzir problemas com a maneira como os arrays eram usados.

No que diz respeito aos operadores de atribuição, a norma C ++ diz o seguinte (C ++ 03 §5.17 / 1):

Existem vários operadores de atribuição … todos requerem um valor modificável como operando à esquerda

Uma matriz não é um valor modificável.

No entanto, a atribuição a um object de tipo de class é definida especialmente (§5.17 / 4):

A atribuição a objects de uma class é definida pelo operador de atribuição de cópias.

Então, nós olhamos para ver o que o operador de atribuição de cópia implicitamente declarado para uma class faz (§12.8 / 13):

O operador de atribuição de cópia definido implicitamente para a class X executa a atribuição de membros de seus subobjects. … Cada subobject é atribuído da maneira apropriada ao seu tipo:

– se o subobject é uma matriz, cada elemento é atribuído, da maneira apropriada ao tipo de elemento

Portanto, para um object de tipo de class, as matrizes são copiadas corretamente. Observe que, se você fornecer um operador de atribuição de cópia declarado pelo usuário, não poderá tirar proveito disso e terá que copiar o elemento elemento por elemento.


O raciocínio é semelhante em C (C99 §6.5.16 / 2):

Um operador de atribuição deve ter um valor modificável como seu operando esquerdo.

E §6.3.2.1 / 1:

Um lvalue modificável é um lvalue que não tem tipo de array … [outras restrições seguem]

Em C, a atribuição é muito mais simples que em C ++ (§6.5.16.1 / 2):

Na atribuição simples (=), o valor do operando direito é convertido no tipo da expressão de atribuição e substitui o valor armazenado no object designado pelo operando esquerdo.

Para atribuição de objects do tipo struct, os operandos esquerdo e direito devem ter o mesmo tipo, portanto, o valor do operando direito é simplesmente copiado no operando esquerdo.

Neste link: http://www2.research.att.com/~bs/bs_faq2.html há uma seção sobre atribuição de matriz:

Os dois problemas fundamentais com matrizes são que

  • uma matriz não sabe seu próprio tamanho
  • o nome de uma matriz é convertido em um ponteiro para seu primeiro elemento na menor provocação

E eu acho que essa é a diferença fundamental entre arrays e structs. Uma variável de matriz é um elemento de dados de baixo nível com autoconhecimento limitado. Fundamentalmente, é um pedaço de memory e uma maneira de indexá-lo.

Então, o compilador não pode dizer a diferença entre int a [10] e int b [20].

As estruturas, no entanto, não têm a mesma ambigüidade.

Eu sei, todos que responderam são especialistas em C / C ++. Mas eu pensei, esta é a principal razão.

num2 = num1;

Aqui você está tentando mudar o endereço base da matriz, o que não é permitido.

e, claro, struct2 = struct1;

Aqui, o object struct1 é atribuído a outro object.