简体   繁体   English

为什么我的自定义迭代器需要基于范围的for循环中的调用运算符?

[英]Why does my custom iterator require a call operator in range based for loops?

Link to the mcve . 链接到mcve

We define a matrix to be iterable both by rows and by columns. 我们定义一个矩阵,使其既可以按行又可以按列进行迭代。 Here's the implementation of the row-wise iterator: 这是逐行迭代器的实现:

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;
};

Iterating over our matrix is implemented using a Range object define as follows: 使用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); }

This is basically our usage code: 这基本上是我们的用法代码:

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;
}

I compiled the above MCVE and got the following compiler errors: 我编译了上面的MCVE并收到以下编译器错误:

  • Visual Studio 2013 (all on line 42): 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 (on line 42): GCC 5.1(在线42):

     error: no match for call to '(RowIterator<float>) ()' 
  • Clang 3.7.0 (on line 42): 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> >' 

All compilers are searching for a call operator. 所有编译器都在搜索调用运算符。 Why? 为什么? 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 . 据我了解 ,上述迭代器为范围循环提供了最小的接口, 并且在使用来自cppreference.com的语法等效代码时可以使用

While writing this question I came up with the solution ( rubber SO debugging ?): the compiler first checks for the members Range::begin and Range::end and tries to invoke those leading to the missing call operator. 在写这个问题时,我想出了解决方案( 橡胶SO调试吗?):编译器首先检查成员Range::beginRange::end并尝试调用那些导致缺少调用运算符的成员。 None of the tested compilers indicated this clearly in their error messages[1]. 没有经过测试的编译器在其错误消息中明确指出了这一点[1]。 The fix is to simply rename them: 解决方法是简单地重命名它们:

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; }

}

The requirements on class Range are well defined (source: cppreference.com , emphasis mine): 明确定义了对Range的要求(来源: cppreference.com ,重点是我的):

begin_expr and end_expr are defined as follows: begin_exprend_expr定义如下:

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) 1如果range_expression是数组类型的表达式,则begin_expr__rangeend_expr(__range + __bound) ,其中__bound是数组中元素的数量(如果数组的大小未知或类型不完整,则程序为格式不正确)

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() ; 2如果range_expression是类C类型的表达式,并且具有名为begin的成员和/或名为end的成员(不管此类成员的类型或可访问性),则begin_expr__range.begin()end_expr__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). 3否则, begin_exprbegin(__range)end_exprend(__range) ,这是通过与参数相关的查找找到的(不执行非ADL查找)。

[1]: Clang actually came close, though even its message is ambiguous: I thought it was (adl) looking up details::begin(Range) instead it was looking straight at Range::begin . [1]:Clang实际上接近了,尽管它的消息是模棱两可的:我以为是(adl)查找details::begin(Range)而不是它直接看了Range::begin

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM