Почему мой пользовательский итератор требует оператора вызова в диапазоне, основанном на циклах?

Ссылка на 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.

1 ответ

Решение

При написании этого вопроса я придумал решение (отладка резины SO?): Компилятор сначала проверяет членов 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, выделение мое):

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 не выполняется).

[1]: Clang действительно приблизился, хотя даже его сообщение неоднозначно: я думал, что это (adl) смотрит вверх details::begin(Range) вместо этого он смотрел прямо на Range::begin,

Другие вопросы по тегам