Looping like that shouldn't be too slow (there is no "copying", just setting elements). But yes, it has the downside that it is harder to make generic for ITensors with an arbitrary number of indices. We definitely agree we should have a better operation for doing this, and it is something that we have discussed as an important feature to add.
One thing I can point out that could make this operation slightly more convenient is that you can also index into an ITensor with integers, not just IndexVals, as long as you have "ordered" the ITensor data correctly:
A = order(A.i,j,k); // Make sure the data of A is ordered according to Indices i,j,k
B = order(B,i,j); // Make sure the data of B is ordered according to Indices i,j
for(int ii = 1; ii <= i.m(); ii++)
for(int jj = 1; jj <= j.m(); jj++)
A.set(ii,jj,2, B.real(ii,jj));
One of the main design choices is how to deal with more general types of slicing, for example if one of the dimensions you were slicing into was a range of values, not just a fixed value. For example, we may have to introduce an "IndexRange" object that generalizes an IndexVal, for example something like `i(2,4)` would represent the Index `i` over the range of values `2,3,4`. Then, the question is, with these types of objects is there a nice way to deal with block-sparse tensors, where the indexing is not as straightforward (i.e. how do you in general make sure two IQTensor slices are compatible with each other).
A more automated way to iterate over all of the non-zero IndexVals of an ITensor/IQTensor is also good suggestion, but again requires some thinking for how to do it in general for sparse tensors. In the longer term, we are working on porting ITensor to Julia, which has a lot of sophisticated Array indexing which we hope to leverage to make some of these operations easier to implement for ITensor.