为什么我的自定义迭代器需要基于范围的 for 循环调用运算符?
Why does my custom iterator require a call operator in range based for loops?
Link 到 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>) ()'
Clang 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> >'
所有编译器都在搜索调用运算符。 为什么? As I understand, the above iterator provides the minimal interface for ranged loops and it works when using the syntactical equivalence code from cppreference.com.
在写这个问题时我想到了解决方案(rubber SO debugging?):编译器首先检查成员 Range::begin
和 Range::end
并尝试调用那些导致丢失的调用操作符的方法。 None 的测试编译器在他们的错误消息中清楚地指出了这一点 [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; }
}
class Range
的要求定义明确(来源:cppreference.com,强调我的):
begin_expr and end_expr are defined as follows:
1 If range_expression is an expression of array type, then
begin_expr is __range
and end_expr is (__range + __bound)
, where __bound
is the number of elements in the array (if the array
has unknown size or is of an incomplete type, the program is
ill-formed)
2 If range_expression is an expression of a class type C that has a
member named begin
and/or a member named end
(regardless of the
type or accessibility of such member), then begin_expr is
__range.begin()
and end_expr is __range.end()
;
3 Otherwise, begin_expr is begin(__range)
and end_expr is
end(__range)
, which are found via argument-dependent lookup (non-ADL
lookup is not performed).
[1]:Clang 实际上接近了,尽管它的信息也含糊不清:我认为它是 (adl) 查找 details::begin(Range)
而不是直视 Range::begin
.
Link 到 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>) ()'
Clang 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> >'
所有编译器都在搜索调用运算符。 为什么? As I understand, the above iterator provides the minimal interface for ranged loops and it works when using the syntactical equivalence code from cppreference.com.
在写这个问题时我想到了解决方案(rubber SO debugging?):编译器首先检查成员 Range::begin
和 Range::end
并尝试调用那些导致丢失的调用操作符的方法。 None 的测试编译器在他们的错误消息中清楚地指出了这一点 [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; }
}
class Range
的要求定义明确(来源:cppreference.com,强调我的):
begin_expr and end_expr are defined as follows:
1 If range_expression is an expression of array type, then begin_expr is
__range
and end_expr is(__range + __bound)
, where__bound
is the number of elements in the array (if the array has unknown size or is of an incomplete type, the program is ill-formed)2 If range_expression is an expression of a class type C that has a member named
begin
and/or a member namedend
(regardless of the type or accessibility of such member), then begin_expr is__range.begin()
and end_expr is__range.end()
;3 Otherwise, begin_expr is
begin(__range)
and end_expr isend(__range)
, which are found via argument-dependent lookup (non-ADL lookup is not performed).
[1]:Clang 实际上接近了,尽管它的信息也含糊不清:我认为它是 (adl) 查找 details::begin(Range)
而不是直视 Range::begin
.