首页 > 解决方案 > 在 C++20 中,我如何编写一个连续的迭代器?

问题描述

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

让我们假设我们有一个完全符合 C++20 库的实现。我想编写最简单的连续迭代器。

这是我的第一次尝试。

#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

#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可以保证它是连续的,只使用成员和朋友以及可以在类的主体中定义的东西吗?就像, ifMyIterator是在一些深度嵌套的命名空间中定义的,我不想为了打开namespace std.


编辑添加:Glen Fernandes 告诉我有一个更简单的方法——我应该像这样添加一个element_typetypedef (而且我可以iterator_traits在 C++20 中删除 3 个大 5 类型定义。)这看起来更好!

#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!

此外,我还看到了一些关于使用成员 typedefiterator_concept而不是iterator_category. 为什么我想提供MyIterator::iterator_concept?(我不是在这里要求完整的历史解释;只是一个简单的最佳实践指南“不,忘记iterator_concept”或“是的,因为它对 X 有帮助”会很好。)

标签: c++stliteratorc++20contiguous

解决方案


C++ 概念作为语言特性的主要好处之一是概念定义告诉您需要提供什么。std::contiguous_iterator也不例外。是的,您可能需要深入挖掘 10 层以上的其他概念定义,才能了解您需要提供的基本术语。但它们就在语言中。

惠特,连续迭代器是随机访问迭代器:

  1. 来自谁的标签contiguous_iterator
  2. reference_type是对其的左值引用value_type(即:不是代理迭代器或生成器)。
  3. 为此,可以调用标准库函数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{});

推荐阅读