10

First I specify A as a struct, and two other structs: B having the same order of elements, and C having a different order of elements.

A.x = 11;
A.y = 11;

B.x = 21;
B.y = 22;

C.y = 31;   %// Note that I am specifying
C.x = 32;   %// y first and x second

A = B;      %// Works fine
A = C;      %// Works fine

Assigning A to B and to C works, which is the behavior I expect from structs - order of elements should not matter.

Now I specify A as a struct array instead of a struct and try to assign one of its elements to B and C respectively:

clear;

A(1).x = 11;
A(1).y = 12;

B.x = 21;
B.y = 22;

C.y = 31;   %// Note that I am specifying
C.x = 32;   %// y first and x second

A(1) = B;   %// Works fine
A(1) = C;   %// Error!

Suddenly MATLAB is complaining with the error:

subscripted assignment between dissimilar structures

Does anyone have any idea why this happens and how to fix it in an elegant way?

Suever
  • 64,497
  • 14
  • 82
  • 101
MGA
  • 1,658
  • 15
  • 28

2 Answers2

11

This is likely happening because the built-in subsasgn call for the struct probably just compares the output of fieldnames (which is dependent upon field order) for the source and destination structs and does not perform any sorting prior to the comparison (likely because of the performance hit of sorting two cell arrays for every assignment). If there is a difference (as in the case you've shown) then an error is thrown and the assignment is aborted.

The easiest way to get around this is to use orderfields on the source struct and specify that you want the ordering to match the destination struct's using the second input argument.

A = struct('x', 11, 'y', 12);
B = struct('y', 21, 'x', 22);

%// Ensure that the fields of B are in the same order as they are in A
A(1) = orderfields(B, A);

In my personal opinion, I think that subsasgn should do this itself for struct inputs because the operation is relatively fast (as there is no sorting and the underlying data of the struct isn't copied) but allows for more flexible struct assignment.

If, on the other hand you aren't doing direct assignment, but simply want to append two structs, the order of the fields does not matter and the order of the fields is inherited from the first struct that is encountered.

%// Uses the ordering of the fields in A
C = cat(1, A, B);

%// Uses the ordering of the fields in B
D = cat(1, B, A);

Update

I just noticed that you showed in your original post that the following worked because the order didn't matter.

A = B;

This works because this assignment is not dependent upon the datatype of A. In this scenario, MATLAB removes the data that A was referencing before the assignment and then re-assigns A it to the point to B. We could even make A a cell array and perform the above assignment with no issues.

A = cell(2);
B = struct('y', 21, 'x', 22);

%// No errors here!
A = B; 

This assignment does not make a call to subsasgn (which deals only with subscript assignment) and therefore would not be expected to exhibit the issue you encountered.

Suever
  • 64,497
  • 14
  • 82
  • 101
  • 3
    Okay, that solves it, but it still feels hacky. This seems like a flaw in MATLAB to me. (Still upvoted because it solves the issue) – MGA Apr 28 '16 at 20:48
  • @MGA I updated it to show how you can only use `orderfields` on one of the structs. But yes, I'm not completely sure why MATLAB behaves this way. It's likely because internally the output of `fieldnames` (which is not sorted) of the two structs is compared during the assignment. The other alternative is to assign within a loop, but that's likely less performant. – Suever Apr 28 '16 at 20:51
  • 2
    @MGA: Would result in a more convenient but slower. It would require sorting which is expensive for many fields. – Daniel Apr 28 '16 at 20:59
  • Sounds like a flaw in the automatic type detections. For some reason Matlab cannot deduce that it is the same struct. Maybe due to different alignment or something. Anyway I do not think that it is a good thing to automatically reorder the fields. Generally I would say this is a programming error. In case the user does not predefiene the struct I would say that he can neither expect Matlab to do a lot of extra checks to determine if the struct is of the same type or not. You already pay for too much that you do not use in Matlab. – patrik Apr 29 '16 at 06:15
  • 1
    @patrik I agree that at the end of the day, consistent ordering should be ensured by the programmer. I just think that the different behavior of `subsasgn` and `cat` with regards to field ordering is slightly odd. If ordering matters, at least make it consistent. – Suever Apr 29 '16 at 12:27
4

One way that I have solved this in the past is to create a "null" version of the structure, similar to creating a constructor for an object.

%% define null struct
null_struct.x = 0;
null_struct.y = 0;

%% Now, initialize all structs with it
A=null_struct;
B=null_struct;
C=null_struct;

%% You can even initialize large arrays
Z(1:1000, 1:1000) = null_struct;

Then, you can populate the structures in any order you like. You can even pass the "empty" structures into a function, and allow the function to populate the values, and the function doesn't have to be careful about the order the values are assigned.

A(1).x = 11;
A(1).y = 12;

B.x = 21;
B.y = 22;

C.y = 31;   % Note that I'm specifying
C.x = 32;   % y first and x second

A(1) = B;   % Works fine
A(1) = C;   % Also works fine!

Initializing your data structures is very good programming practice, and for large struct arrays, it actually saves a lot of time to do the initialization up front. Even if you have to initialize more elements than needed, and truncate the array at the end, it is generally worth it.

EDIT: Explanation for your error: The reason that MATLAB throws an error in your original example is that internally (in the C-code backend), MATLAB stores the field names in an ordered array of character strings, and maps the names to corresponding field indices. When you perform an assignment like

A = C;

MATLAB first checks that the two lists of fieldnames match, which requires that the lists be identical, including in the same order. If they are, then it maps the field values in order from the rhs to the lhs.

gariepy
  • 3,576
  • 6
  • 21
  • 34