2

Let's say I have a Matrix class I've already implemented.

Matrix<float> mat(30, 30);
for(size_t row = 0; row < mat.rows(); row++) {//Assume Row-Major ordering for performance reasons
    for(size_t column = 0; column < mat.columns(); column++) {
        mat(row, column) = float(row+1) / float(column+1);
    }
}

I'd like to add to this implementation some concepts of "Row Iterator" and "Column Iterator". Some kind of interface like the following:

//public:
    row_iterator begin_row(size_t row); //Is this intuitive?
    const_row_iterator begin_row(size_t row) const;
    const_row_iterator cbegin_row(size_t row) const;
    row_iterator end_row(size_t row); 
    const_row_iterator end_row(size_t row) const;
    const_row_iterator cend_row(size_t row) const;
    //Same for column iterators

The issue is that it's not clear to me what behavior is intuitive for most users. My instinct is that a "Row Iterator" iterates "within" the row, i.e,

auto begin = mat.begin_row(1);
auto end = mat.end_row(1);
for(; begin < end; ++begin) *begin = 5.f;//Fills the second row with the value 5.

However, it occurs to me that some users might expect a "Row Iterator" to iterate "across" rows, i.e.

auto begin = mat.begin_row(1);
auto end = mat.end_row(1);
for(; begin < end; ++begin) *begin = 5.f;//Fills the second column with the value 5.

Implementing either version of this code is (theoretically) trivial, but which behavior seems more intuitive to an average user? Is there a better design that avoids this ambiguity?

  • Your style is suggestive of STL Iterators (see here), but there are some oddities. For example, your loop variable is named begin when in fact it should be called something else, perhaps currentIter. – Robert Harvey Oct 13 '17 at 20:41
  • @RobertHarvey The two code-snippets at the end of my post are meant to be [example] user-code, not library code. – Xirema Oct 13 '17 at 20:43
  • 1
    Wouldn't a matrix iterator iterate cells? And then row first or column first could be different kinds of iterators. – Mike Oct 13 '17 at 21:19
  • It depends on what the typical use cases are for your matrices. Matrices can be used for many things, e.g. mathematical matrices have rows and columns, images have x and y. What you're looking for is 2D array slices (mathematically "submatrix") and their implementation details. Some 2D array slicing can be shallow-copied; some require deep copy. – rwong Oct 13 '17 at 22:21
  • Example: see OpenCV Mat.row(int r). The R-th row of an M-by-N matrix is a row matrix of size 1-by-N. – rwong Oct 13 '17 at 22:24
7

I would personally expect that this iterator would iterate “across” different rows and not “within” the same row. The reason why I think this is because when I read “row_iterator” I think of a container (vector, list, map, etc…) that contains different “rows” that I iterate across. Thus, by incrementing the iterator, I now point to a different row. Conversely, if i read "row::iterator" I think of iterating "within" the elements of the same row.

It’s hard to say which user will think one way or the other, however. Even if it could be determined what the average user would interpret it to mean, there would be the occasional outlier that would misunderstand. In my experience, even the best designed interfaces can be misunderstood by at least someone. I don't think that there is really a "best" answer here.

Whichever implementation you choose and whatever name you give to this iterator, I would suggest adding some brief documentation in the header file that contains its definition that clarifies what a row_iterator is. If you’re packing this into some kind of library, ensure the same documentation is put into the user manual somewhere where the user of the Matrix class will likely see it.

The key, in my mind at least, is to clearly document what it does and make the documentation visible to the end user.

4

Well, I suggest re-thinking your design.
Why? Simply because it cannot be used in for-range-loops.

Consider something more along the lines of:

public:
    // Access by row
    row_view<const T> rows() const;
    row_view<T> rows();
    // Access by col
    col_view<const T> cols() const;
    col_view<T> cols();
    // Access by cell
    iterator<T> begin();
    iterator<const T> begin() const;
    iterator<T> end();
    iterator<const T> end() const;
1

As Leon Bambrick once said:

There are 2 hard problems in computer science: cache invalidation, naming things, and off-by-1 errors.

When you name something row_iterator and then you also name something else column_iterator, it is only logical that one would assume that the former iterates through collection of rows, and the latter through collection of columns. Thus, the single iteration result would be a row (in the former case), or a column (in the latter case).

Consider giving more intuitive names to the iterators and/or documenting in more details (and some examples) how each of those should be used.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.