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?

Xirema
  • 533

3 Answers3

8

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.

Ryan
  • 613
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;
Deduplicator
  • 9,209
2

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.

Vladimir Stokic
  • 2,973
  • 17
  • 26