在 C++20 中,如何编写连续迭代器?

问题描述

C++20 对 std::contiguous_iterator_tag 有明确的库支持。一些 STL 算法(例如 std::copy)可以在连续迭代器上表现得更好。但是,我不清楚程序员应该如何访问这个新功能

为了论证起见,让我们假设我们有一个完全符合 C++20 的库实现。我想写一个最简单的连续迭代器。

Here's my first attempt.

#include <iterator>

class MyIterator {
    int *p_;
public:
    using value_type = int;
    using reference = int&;
    using pointer = int*;
    using difference_type = int;
    using iterator_category = std::contiguous_iterator_tag;
    int *operator->() const;
    int& operator*() const;
    int& operator[](int) const;
    MyIterator& operator++();
    MyIterator operator++(int);
    MyIterator& operator--();
    MyIterator operator--(int);
    MyIterator& operator+=(int);
    MyIterator& operator-=(int);
    friend auto operator<=>(MyIterator,MyIterator) = default;
    friend int operator-(MyIterator,MyIterator);
    friend MyIterator operator+(MyIterator,int);
    friend MyIterator operator-(MyIterator,int);
    friend MyIterator operator+(int,MyIterator);
};

namespace std {
    int *to_address(MyIterator it) {
        return it.operator->();
    }
}

static_assert(std::contiguous_iterator<MyIterator>);  // FAILS

这在 GCC/libstdc++ 和 MSVC/STL 上都失败了;但它应该吗?

对于我的下一次尝试,我专门研究了 pointer_traits<MyIterator>MyIterator 实际上不是一个指针,所以除了库需要的一个函数之外,我没有在 pointer_traits 中放入任何东西。 This is my second attempt

#include <iterator>

class MyIterator {
    ~~~
};

template<>
struct std::pointer_traits<MyIterator> {
    int *to_address(MyIterator it) {
        return it.operator->();
    }
};

static_assert(std::contiguous_iterator<MyIterator>);  // OK!

这是我应该做的吗?感觉非常hacky。 (需要明确的是:我的第一次失败尝试感觉非常糟糕。)

我错过了一些更简单的方法吗?

特别是,有没有办法让 MyIterator 本身保证它是连续的,只使用成员和朋友以及可以在类的主体中定义的东西?比如,如果 MyIterator 是在某个深度嵌套的命名空间中定义的,并且我不想为了打开 namespace std 而一直突破到顶级命名空间。


编辑添加:Glen Fernandes 告诉我一种更简单的方法——我应该只添加一个 element_type typedef,like this!(我可以删除 3 个 { {1}}' C++20 中的 5 个大类型定义。)这看起来更好!

iterator_traits

此外,我还看到了一些关于使用成员 typedef #include <iterator> class MyIterator { int *p_; public: using value_type = int; using element_type = int; using iterator_category = std::contiguous_iterator_tag; int *operator->() const; int& operator*() const; int& operator[](int) const; MyIterator& operator++(); MyIterator operator++(int); MyIterator& operator--(); MyIterator operator--(int); MyIterator& operator+=(int); MyIterator& operator-=(int); friend auto operator<=>(MyIterator,MyIterator); }; static_assert(std::contiguous_iterator<MyIterator>); // OK! 而不是 iterator_concept内容。为什么我可能想要提供 iterator_category? (我不是在这里要求提供完整的历史解释;只是一个简单的最佳实践指南“不要忘记 MyIterator::iterator_concept”或“是的,因为它有助于 X”就很好。)

解决方法

C++ 概念作为语言特性的主要好处之一是概念定义告诉您您需要提供什么。 std::contiguous_iterator is no different。是的,您可能需要深入研究 10 多个其他概念定义层才能深入了解您需要提供的基本术语。但它们就在语言中。

简而言之,连续迭代器是随机访问迭代器:

  1. 其标签源自contiguous_iterator
  2. reference_type 是对其 value_type 的左值引用(即:不是代理迭代器或生成器)。
  3. 可以调用 standard library function std::to_address 将任何有效的迭代器(即使是不可解引用的迭代器)转换为指向迭代器引用的元素或指向最后一个指针的指针。

不幸的是,无法通过直接特化 std::to_address 来满足 #3。您必须为您的迭代器类型使用 specialize pointer_traits::to_address 或给您的迭代器一个 operator-> 重载。

后者更容易做到,尽管 operator-> 没有另外要求 std::contiguous_iterator,但对于这种迭代器类型是有意义的:

class MyIterator
{
 ...
  int const *operator->() const;
};

仅供参考:如果您基于概念约束模板,而不是仅仅static_assert对其进行约束,那么大多数编译器会为您提供更多有用的错误消息。像这样:

template<std::contiguous_iterator T>
void test(const T &t);

test(MyIterator{});
,

有两种方法可以支持 std::to_address。一种是:

namespace std {

template<>
struct pointer_traits<I> {
    static X* to_address(const I& i) {
        // ...
    }
};

}

注意上面的 static

第二种方法,正如 Glen 向您指出的那样,是简单地定义 I::operator-> 并确保 std::pointer_traits<I> 的主模板有效。这仅要求 std::pointer_traits<I>::element_type 有效。

Nicol Bolas 的回答不正确,因为这不是您为用户定义的类型自定义 std::to_address 的方式。从 C++20 开始(由于 P0551),禁止在名称空间 std 中专门化您未明确允许的函数模板。

您不能专攻std::to_address。不过,您可以提供 std::pointer_traits<I>::to_address,如果存在,std::to_address 会调用它。

,

我问题中的最后一个版本似乎是最正确的。只有一个微妙之处需要注意:

  • MyIterator::value_type 应该是指针对象的 cv-unqualified 类型,这样有人可以写 value_type x; x = *it;
  • MyIterator::element_type 应该是指针对象的 cv-qualified 类型,这样有人可以写 element_type *ptr = std::to_address(it);

因此对于常量迭代器,element_type 不仅仅是 value_type 的同义词——它还是 const value_type 的同义词!如果你不这样做,标准库内部的东西就会爆炸。

Here's a Godbolt proof-of-concept. (这不是一个完整的生态系统,因为您确实希望 MyIterator 可以隐式转换为 MyConstIterator,并且可能使用模板来消除一些重复。我有一篇关于该主题的散漫博客文章 here。)

#include <iterator>

class MyIterator {
    int *p_;
public:
    using value_type = int;
    using element_type = int;
    using iterator_category = std::contiguous_iterator_tag;
    int *operator->() const;
    int& operator*() const;
    int& operator[](int) const;
    MyIterator& operator++();
    MyIterator operator++(int);
    MyIterator& operator--();
    MyIterator operator--(int);
    MyIterator& operator+=(int);
    MyIterator& operator-=(int);
    friend auto operator<=>(MyIterator,MyIterator) = default;
    friend int operator-(MyIterator,MyIterator);
    friend MyIterator operator+(MyIterator,int);
    friend MyIterator operator-(MyIterator,int);
    friend MyIterator operator+(int,MyIterator);
};

static_assert(std::contiguous_iterator<MyIterator>);  // OK!

class MyConstIterator {
    const int *p_;
public:
    using value_type = int;
    using element_type = const int;
    using iterator_category = std::contiguous_iterator_tag;
    const int *operator->() const;
    const int& operator*() const;
    const int& operator[](int) const;
    MyConstIterator& operator++();
    MyConstIterator operator++(int);
    MyConstIterator& operator--();
    MyConstIterator operator--(int);
    MyConstIterator& operator+=(int);
    MyConstIterator& operator-=(int);
    friend auto operator<=>(MyConstIterator,MyConstIterator) = default;
    friend int operator-(MyConstIterator,MyConstIterator);
    friend MyConstIterator operator+(MyConstIterator,int);
    friend MyConstIterator operator-(MyConstIterator,int);
    friend MyConstIterator operator+(int,MyConstIterator);
};

static_assert(std::contiguous_iterator<MyConstIterator>);  // OK!

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...