为什么编写反向迭代器会影响迭代器是否为随机访问迭代器?

Why writing reverse iterator can affect whether an iterator is random-access-iterator or not?

我想写一个带有随机访问迭代器的容器:

#include <cstddef>
#include <iterator>
#include <concepts>

namespace foo
{
    struct container
    {
        struct iter
        {
            using difference_type = std::ptrdiff_t;
            using pointer = int*;
            using reference = int&;
            using value_type = int;
            using iterator_category = ::std::random_access_iterator_tag;

            iter& operator++();
            iter operator++(int);
            iter& operator--();
            iter operator--(int);

            iter operator+(difference_type) const;
            iter operator-(difference_type) const;
            iter& operator+=(difference_type);
            iter& operator-=(difference_type);
            bool operator==(const iter&) const;
            bool operator!=(const iter&) const;
            bool operator<(const iter&) const;
            bool operator>(const iter&) const;
            bool operator<=(const iter&) const;
            bool operator>=(const iter&) const;
            difference_type operator-(const iter&) const;

            int& operator*() const;
            int& operator[](difference_type) const;
        };

        // code for reverse iterator, now it is comment out

        /* using riter = std::reverse_iterator<iter>;

        iter end();
        riter rbegin()
        {
            return std::reverse_iterator(this->end());
        }
        */
    };

    inline container::iter operator+(container::iter::difference_type, const container::iter&);
}

int main(void)
{
    static_assert(std::random_access_iterator<foo::container::iter>, "");
    return 0;
}

然后我用g++-11 -std=c++20编译,可以编译成功

但是如果我想为这个容器写一个反向迭代器,也就是取消注释那些反向迭代器的代码,就会出现编译错误:

<source>:55:2: error: static_assert failed ""
        static_assert(std::random_access_iterator<foo::container::iter>, "");
        ^                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:55:21: note: because 'foo::container::iter' does not satisfy 'random_access_iterator'
        static_assert(std::random_access_iterator<foo::container::iter>, "");
                           ^
/opt/compiler-explorer/gcc-11.2.0/lib/gcc/x86_64-linux-gnu/11.2.0/../../../../include/c++/11.2.0/bits/iterator_concepts.h:669:8: note: because '__n + __j' would be invalid: invalid operands to binary expression ('const iter_difference_t<foo::container::iter>' (aka 'const long') and 'const foo::container::iter')
        { __n +  __j } -> same_as<_Iter>;
              ^
1 error generated.

我也使用 Clang 13,但得到了相同的结果。那么迭代器应该怎么写才正确呢?

仅仅因为您给迭代器一个 random_access_iterator_tag 类别并不能使它成为随机访问迭代器。

您必须提供所有操作。

编译器错误告诉您表达式 3 + iter_var 无效(其中 iter_var 是您的迭代器之一)。

Remy 提供的 link 列出了“必须工作”的操作。

这里的谜团是为什么添加几行代码 不改变 foo::container::iter 的定义会导致 static_assert 失败。

这是因为 std::reverse_iterator 的构造函数的实现在注释函数的主体中评估 concept std::random_access_iterator 以确定要创建哪种反向迭代器。

发生这种情况时,operator+(difference_type, iter) 尚未声明,因为 它在您的代码中更靠后 ,因此您的 container::iter 最初无法满足概念。

稍后,您声明所需的 operator+,如果不是标准的以下部分,您的类型现在将满足 concept std::random_access_iterator:[temp.constr.atomic]

... If, at different points in the program, the satisfaction result is different for identical atomic constraints and template arguments, the program is ill-formed, no diagnostic required.

在您的代码的不同点,concept std::random_access_iterator 有不同的满意结果!所以你的代码格式错误,不需要诊断。 (GCC 确实诊断了这个 - clang 只是给你它第一次评估这个概念时的错误信息,这就是你感到困惑的原因)。

您可以通过在使用 std::reverse_iterator 之前移动您的 operator+ 声明来解决此问题。例如:

struct container {
    struct iter {
        // all your methods, as before
        
        friend iter operator+(difference_type, iter);
    }

    using riter = std::reverse_iterator<iter>;

    iter end();
    riter rbegin();
}