如何解决在 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
中放入任何东西。 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 多个其他概念定义层才能深入了解您需要提供的基本术语。但它们就在语言中。
简而言之,连续迭代器是随机访问迭代器:
- 其标签源自
contiguous_iterator
- 其
reference_type
是对其value_type
的左值引用(即:不是代理迭代器或生成器)。 - 可以调用 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!
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。