简体   繁体   English

输入/输出迭代器的不等式运算符的要求

[英]Requirements of the inequality operator of an input/output iterator

I'm creating a small tile based game. 我正在创建一个基于小图块的游戏。 Items in the game store their location in a matrix of buckets. 游戏中的物品将其位置存储在水桶矩阵中。 I've implemented this as a class template named Grid which contains a bucket class named Tile . 我已将其实现为名为Grid的类模板,其中包含名为Tile的存储桶类。

The Grid is essentially just a wrapper around a std::vector with various accessor methods for converting coords into index keys. Grid本质上只是一个std::vector的包装,具有各种用于将坐标转换为索引键的访问器方法。 It also forwards the vector's iterators so that I can loop over all Tiles in the Grid . 它还转发了向量的迭代器,以便我可以遍历Grid所有Tiles

Sometimes though I need to only iterate over a subsection of the Grid . 有时,尽管我只需要遍历Grid一个子部分。 So I've implemented a small class named Section which takes two sets of coords in the constructor to define an AABB. 因此,我实现了一个名为Section的小类,该类在构造函数中使用两组坐标来定义AABB。 The begin() and end() methods of Section return input/output iterators for looping over all of the tiles inside the AABB. Sectionbegin()end()方法返回输入/输出迭代器,以循环AABB内的所有图块。

Its all working but I'm trying to keep the performance of the iterators as close to a nested loop as possible. 一切正常,但我试图使迭代器的性能尽可能接近嵌套循环。 Basically using a range based for on a Section shouldn't be too much more expensive than: 基本上使用基于Section的范围应该不会比:

for (size_t y = 0, end_y = NUM; y < end_y; ++y)
{
    for (size_t x = 0, end_x = NUM; x < end_x; ++x)
    {
        auto& tile = grid[coords_to_key(x, y)];
    }
}

This brings me to the point of the question. 这使我明白了问题的重点。 I want the inequality operator to be as simple as possible so I've implemented it like so: 我希望不等式运算符尽可能简单,因此我已将其实现为:

bool operator!=(const Section_Iterator& other) const
{
    return m_coords.y < other.m_coords.y;
}

Since the iterator scans each row in the Section sequentially we know that we're 'past the end' when iterator.y >= end.y . 由于迭代器按顺序扫描Section每一行,所以我们知道当iterator.y >= end.y时,我们在“过去了”。 This means my inequality operator works for ranged based for loops since under the hood they just check that iterator != end . 这意味着我的不等式运算符适用于基于范围的for循环,因为在幕后他们只检查iterator != end

The implementation of the operator looks weird though. 操作符的实现看起来很奇怪。 Like really weird. 真的很奇怪。 For example iterator != ++iterator may be true or false. 例如, iterator != ++iterator可以为true或false。 It depends on whether the pre-increment caused the iterator to jump to the next row. 这取决于预增量是否导致迭代器跳到下一行。

I've been looking at the standard and I think I'm in the clear since they make the distinction between equality and equivalence. 我一直在研究标准,我想我很清楚,因为它们区分了平等和等效。

From http://en.cppreference.com/w/cpp/concept/InputIterator 来自http://en.cppreference.com/w/cpp/concept/InputIterator

Note, "in the domain of ==" means equality comparison is defined between the two iterator values. 注意,“在==的域中表示两个迭代器值之间定义了相等比较。 For input iterators, equality comparison does not need to be defined for all values, and the set of the values in the domain of == may change over time. 对于输入迭代器,无需为所有值都定义相等比较,并且==域中的值集可能会随时间变化。

From http://en.cppreference.com/w/cpp/concept/OutputIterator 来自http://en.cppreference.com/w/cpp/concept/OutputIterator

Equality and inequality may not be defined for output iterators. 可能没有为输出迭代器定义相等和不相等。 Even if an operator== is defined, x == y need not imply ++x == ++y. 即使定义了operator ==,x == y也不一定意味着++ x == ++ y。

Honestly though, standardese makes my head spin. 老实说,标准语言使我头晕。 Is what I'm doing legal? 我在做什么合法吗?

After more researching it turns out that what I was doing was not legal according to the standard. 经过更多的研究,事实证明,按照该标准,我在做什么是不合法的。

An input iterator must be EqualityComparable . 输入迭代器必须是EqualityComparable This means that: 这意味着:

  • For all values of a, a == a yields true. 对于a的所有值,a == a得出true。
  • If a == b, then b == a 如果a == b,则b == a
  • If a == b and b == c, then a == c 如果a == b和b == c,则a == c

With my current equality operator a == b does not mean that b == a . 对于我当前的相等运算符, a == b并不意味着b == a

To solve my problem I looked at std::istream_iterator , it is an implementation of an input iterator and naturally anything it does must conform to the standard. 为了解决我的问题,我看了std::istream_iterator ,它是输入迭代器的实现,自然地,它所做的任何事情都必须符合标准。 The behaviour of its equality operator is described like this: 其相等运算符的行为描述如下:

Checks whether both lhs and rhs are equal. 检查lhs和rhs是否相等。 Two stream iterators are equal if both of them are end-of-stream iterators or both of them refer to the same stream 如果两个流迭代器都是流结束迭代器,或者它们都引用相同的流,则两个流迭代器相等

Basically, if both iterators are valid they compare equal. 基本上,如果两个迭代器均有效,则它们比较相等。 If they are both 'past the end' they compare equal. 如果它们都“过去了”,则它们比较相等。 If one is valid but one is 'past the end' they are not equal. 如果一个有效,但一个“最后”,则不相等。

Applying the same logic to my Section::iterator was easy. 将相同的逻辑应用于我的Section::iterator很容易。 The iterator now contains a bool, m_valid . 现在,迭代器包含一个布尔值m_valid The method begin() always returns an iterator where m_valid == true and the end() method always returns an iterator where m_valid == false . 方法begin()总是返回一个迭代器,其中m_valid == true ,而end()方法总是返回一个迭代器,其中m_valid == false

The iterator's pre-increment operator now tests whether it is past the end and sets the bool accordingly. 现在,迭代器的预增量运算符将测试它是否已结束,并相应地设置布尔值。

Section_Iterator& operator++()
{
    ++m_coords.x;
    if (m_coords.x >= m_section.m_max.x)
    {
        m_coords.x = m_section.m_min.x;
        ++m_coords.y;
        m_valid = (m_coords.y < m_section.m_max.y);
    }

    return *this;
}

The equality operators are now very simple to understand and have consistent behaviour. 现在,相等运算符非常容易理解,并且具有一致的行为。 Any iterator that points to a Tile in the Section is valid and compares equal to any other valid iterator. 指向SectionTile任何迭代器都是有效的,并且与其他任何有效的迭代器进行比较。

bool operator==(const Section_Iterator& other) const
{
    return m_valid == other.m_valid;
}

bool operator!=(const Section_Iterator& other) const
{
    return m_valid != other.m_valid;
}

Honestly I don't know whether what you have done in the above is legal. 老实说,我不知道您在上述行为中是否合法。 It certainly has strange semantics, though, even if it is legal. 即使它是合法的,它当然也具有奇怪的语义。

Instead, I would consider something like this to solve your problem: 相反,我会考虑使用以下方法解决您的问题:

#include <iostream>
#include <vector>

struct Grid
{
    std::vector<int> tiles;
    size_t rows;
    size_t cols;
};

class SectionIterator
{
public:
    SectionIterator(Grid * grid, size_t row, size_t col, size_t endRow) :
            m_row{ row },
            m_col{ col },
            m_startRow{ row },
            m_endRow{ endRow },
            m_grid{ grid }
    {
    }

    SectionIterator & operator++()
    {
        ++m_row;
        if (m_row == m_endRow)
        {
            m_row = m_startRow;
            ++m_col;
        }
        return *this;
    }

    bool operator==(const SectionIterator & other) 
    {
        return (m_grid == other.m_grid) 
                && (m_row == other.m_row)
                && (m_col == other.m_col);
    }

    bool operator!=(const SectionIterator & other) 
    {
        return !(*this == other);
    }

    int & operator*() 
    {
        return m_grid->tiles[m_col * m_grid->rows + m_row];
    }

    int * operator->() 
    {
        return &operator*();
    }
private:
    size_t m_row;
    size_t m_col;
    size_t m_startRow;
    size_t m_endRow;
    Grid * m_grid;

};

struct Section 
{
    SectionIterator m_begin;
    SectionIterator m_end;

    SectionIterator begin() { return m_begin; }
    SectionIterator end() { return m_end; }
};

int main() 
{
    Grid grid{ std::vector<int>{ 1, 2, 3, 4, 5, 6 }, 2, 3 };
    // 1, 3, 5 
    // 2, 4, 6

    // look up start and end row and col
    // end positions are found by looking up row/col of section end and then adding one
    size_t startRow = 0;
    size_t endRow = 2;
    size_t startCol = 1;
    size_t endCol = 3;

    SectionIterator begin = SectionIterator{ &grid, startRow, startCol, endRow };
    // Note that the end iterator actually has startRow as its startRow, not endRow, because operator++ will set the iterator's m_row back to startRow so this will make it equal the end iterator once the iteration is complete
    SectionIterator end = SectionIterator{ &grid, startRow, endCol, endRow };
    for (int v : Section{ begin, end })
    {
        std::cout << v << std::endl;
    }
    return 0;
}

Note that this presupposes that you have some function to translate between your co-ordinates and row/col indexes in the grid. 请注意,这以您具有在网格中的坐标和行/列索引之间进行转换的功能为前提。 Also, the above iterates in a column-major order, but could easily be changed to iterate in a row-major order. 同样,以上内容以列优先顺序进行迭代,但可以轻松更改为以行优先顺序进行迭代。

EDIT 编辑

To clarify how the conversion from floating point coords to indexes would work, consider the following. 为了阐明从浮点坐标到索引的转换将如何工作,请考虑以下内容。

I assume your tiles are defined such that each tile covers a 1x1 square of floating point coordinates. 我假设您的图块已定义为每个图块都覆盖一个1x1平方英寸的浮点坐标。 For example, tile (0, 0) covers the floating point intervals [0.0, 1.0), [0.0, 1.0) and tile (2, 2) covers the intervals [2.0, 3.0), [2.0, 3.0). 例如,图块(0,0)覆盖浮点间隔[0.0,1.0),[0.0,1.0),而图块(2,2)覆盖间隔[2.0,3.0),[2.0,3.0)。 I believe this is how you have described your current setup. 我相信这就是您描述当前设置的方式。

If you wish to iterate all the tiles within the section from (1.2, 1.2) to (4.2, 4.2), first convert these points to row, col indices via truncation: 如果要迭代从(1.2,1.2)到(4.2,4.2)部分中的所有图块,请首先通过截断将这些点转换为row,col索引:

(1.2, 1.2) = tile (1, 1) (4.2, 4.2) = tile (4, 4) (1.2,1.2)=瓷砖(1,1)(4.2,4.2)=瓷砖(4,4)

This means you want to iterate rows in the closed-closed interval [1, 4] and columns in the closed-closed interval [1, 4]. 这意味着您要迭代封闭间隔[1,4]中的行和列封闭闭合[1,4]中的列。 Since iterators like the one above work with closed-open intervals, you must add 1 to the end indexes, such that the values you pass into the iterator represent the intervals [1, 5) for rows and [1, 5) for columns. 由于类似于上述迭代器的迭代器在封闭时间间隔内工作,因此必须在结束索引处加1,这样,传递给迭代器的值就代表行的间隔[1,5)和列的间隔[1,5)。 Note that these intervals are actually the same as the closed-closed interval forms, but the end values represent "one past the last index you want to dereference". 请注意,这些间隔实际上与封闭-封闭间隔形式相同,但是最终值表示“比您要取消引用的最后一个索引晚一个”。

EDIT #2 编辑#2

You indicated that you actually want to make sure that your section finishes on an open interval in floating point coordinates, so that (1.0, 1.0) to (4.0, 4.0) contains 3 tile rows and 3 tile columns, not 4. 您表示实际上是要确保部分以浮点坐标的打开间隔结束,因此(1.0,1.0)至(4.0,4.0)包含3个图块行和3个图块列,而不是4。

You could do this by comparing the end index to the original value and only adding 1 if it's not an integer, so 您可以通过将结束索引与原始值进行比较,并仅在非整数的情况下加1来做到这一点,因此

float coord = ...;
size_t idx = static_cast<size_t>(coord);
constexpr float tolerance = 1e-6f;
if (std::abs(coord - idx) > tolerance)
{
    // Add 1 to make the range include the last tile
    ++idx;
}

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

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