Ссылка на mcve.
Мы определяем матрицу, которая будет повторяться как по строкам, так и по столбцам. Вот реализация строкового итератора:
template<class Real>
class RowIterator {
public:
RowIterator() { }
RowIterator(Real* begin, size_t rows, size_t cols) : begin(begin), rows(rows), cols(cols) { }
Real* operator*() const { return begin; }
Real& operator[](size_t col) const { return begin[col]; }
bool operator!=(const RowIterator& it) const { return begin != it.begin; }
RowIterator& operator++() { begin += cols; --rows; return *this; }
private:
Real* begin;
size_t rows, cols;
};
Итерация по нашей матрице реализована с использованием Range
Объект определяют следующим образом:
namespace details
{
template<class Iterator>
struct Range {
Iterator begin, end;
Range() { }
Range(Iterator begin, Iterator end) : begin(begin), end(end) { }
};
template<class Iterator>
Iterator begin(const Range<Iterator>& range) { return range.begin; }
template<class Iterator>
Iterator end(const Range<Iterator>& range) { return range.end; }
}
using details::Range;
template<class Iterator>
Range<Iterator> make_range(Iterator begin, Iterator end) { return Range<Iterator>(begin, end); }
Это в основном наш код использования:
Range<RowIterator<float>> make_row_range(float* mat, size_t rows, size_t cols) {
return make_range(
RowIterator<float>(mat, rows, cols),
RowIterator<float>(mat + rows * cols, 0, cols));
}
int main() {
size_t rows = 4, cols = 6;
float* mat = new float[rows * cols];
for(size_t i = 0; i < rows * cols; ++i)
mat[i] = (float)i;
auto rowrange = make_row_range(mat, rows, cols);
// this loop works as expected
std::cout << "begin, end" << std::endl;
for(auto b = begin(rowrange), e = end(rowrange); b != e; ++b) {
// using RowIterator<T>::operator[](size_t)
std::cout << "start of row: " << b[0] << std::endl;
}
// this loop produces confusing compiler errors
std::cout << "range based" << std::endl;
for(auto row : rowrange) { // this is line 42
// row is of type float*
std::cout << "start of row: " << row[0] << std::endl;
}
return 0;
}
Я скомпилировал вышеупомянутый MCVE и получил следующие ошибки компилятора:
Visual Studio 2013 (все в строке 42):
error C2064: term does not evaluate to a function taking 0 arguments
error C3536: '$S2': cannot be used before it is initialized
error C3536: '$S3': cannot be used before it is initialized
error C2100: illegal indirection
error C2440: 'initializing' : cannot convert from 'int' to 'float *'
GCC 5.1 (в строке 42):
error: no match for call to '(RowIterator<float>) ()'
Звонок 3.7.0 (в строке 42):
error: type 'RowIterator<float>' does not provide a call operator
note: when looking up 'begin' function for range expression of type 'details::Range<RowIterator<float> >'
Все компиляторы ищут оператора вызова. Зачем? Насколько я понимаю, вышеупомянутый итератор обеспечивает минимальный интерфейс для циклических вычислений и это работает при использовании синтаксического кода эквивалентности из cppreference.com.
При написании этого вопроса я придумал решение (резина ТАК отладка?): компилятор сначала проверяет членов Range::begin
а также Range::end
и пытается вызвать тех, кто ведет к отсутствующему оператору вызова. Ни один из протестированных компиляторов не указал это четко в своих сообщениях об ошибках [1]. Исправление заключается в том, чтобы просто переименовать их:
namespace range
{
template<class Iterator>
struct Range {
// "begin" and "end" have ultra-special meaning in this context!!!
Iterator range_begin, range_end;
Range() { }
Range(Iterator begin, Iterator end) : range_begin(begin), range_end(end) { }
};
template<class Iterator>
Iterator begin(const Range<Iterator>& range) { return range.range_begin; }
template<class Iterator>
Iterator end(const Range<Iterator>& range) { return range.range_end; }
}
Требования к классу Range
хорошо определены (источник: cppreference.com, акцент мой):
[1]: Clang действительно приблизился, хотя даже его сообщение неоднозначно: я думал, что это (adl) смотрит вверхbegin_expr а также end_expr определяются следующим образом:
1 Если range_expression является выражением типа массива, то
begin_expr является__range
а также end_expr является(__range + __bound)
, где__bound
количество элементов в массиве (если массив
имеет неизвестный размер или имеет неполный тип, программа
плохо сформированы)2 Если range_expression является выражением типа C класса, который имеет
член по имениbegin
и / или член по имениend
(независимо от
тип или доступность такого члена), то begin_expr является
__range.begin()
а также end_expr является__range.end()
;3 В противном случае, begin_expr является
begin(__range)
а также end_expr является
end(__range)
, которые находятся через аргумент-зависимый поиск (не ADL
поиск не выполняется).
details::begin(Range)
вместо этого он смотрел прямо на Range::begin
,
Других решений пока нет …