提供运算符+或运算符到双向迭代器有什么缺点吗?

Is there any drawbacks in providing operator+ or operator- to bidirectional iterators?

本文关键字:运算符 什么 缺点 迭代器      更新时间:2023-10-16

双向迭代器没有像随机访问迭代器那样的奢侈品,因此需要依赖于std::nextstd::prev,当有人需要做这样的操作时

std::set<int> s{ 1, 2, 3, 4, 5 };
//std::set<int> s2(s.cbegin(), s.cbegin() + 2); // won't work as there is no operator+ for std::set::const_iterator
std::set<int> s2(s.cbegin(), std::next(s.cbegin(), 2));
//std::set<int> s3(s.cbegin(), s.cend() - 2);  // won't work as there is no operator- for std::set::const_iterator
std::set<int> s3(s.cbegin(), std::prev(s.cend(), 2));

但是,我们可以使用上述std::nextstd::prev来实现这些operator+operator-

#include <set>
#include <iterator>  // std::iterator_traits, std::next, std::prev
template<typename InputIt>
constexpr InputIt operator+(InputIt it,
typename std::iterator_traits<InputIt>::difference_type n)
{
return std::next(it, n);
}
template<typename InputIt>
constexpr InputIt operator-(InputIt it,
typename std::iterator_traits<InputIt>::difference_type n)
{
return std::prev(it, n);
}
int main()
{
std::set<int> s{ 1, 2, 3, 4, 5 };
std::set<int> s2(s.cbegin(), s.cbegin() + 2); // works now
std::set<int> s3(s.cbegin(), s.cend() - 2);   // works now
}
  • 使用这些实现有什么缺点吗?
  • 如果没有,在C++标准库中拥有不是很好吗?

更新

正如@Nicol波拉斯在他的回答中提到的那样,使用InputIt将是一个错误。不过我想证明一下这个想法。无论如何,让我们考虑一个SFINE解决方案,它只接受双向迭代器。除了尼科尔提到的之外,还有其他问题吗?

#include <type_traits>
#include <iterator>  // std::iterator_traits, std::bidirectional_iterator_tag, std::next, std::prev
template<typename BidirIterators>
constexpr auto operator+(BidirIterators it,
typename std::iterator_traits<BidirIterators>::difference_type n)
-> std::enable_if_t<
std::is_same_v<
std::bidirectional_iterator_tag,
typename std::iterator_traits<BidirIterators>::iterator_category
>, BidirIterators
>
{
return std::next(it, n);
}
template<typename BidirIterators>
constexpr auto operator-(BidirIterators it,
typename std::iterator_traits<BidirIterators>::difference_type n)
-> std::enable_if_t<
std::is_same_v<
std::bidirectional_iterator_tag,
typename std::iterator_traits<BidirIterators>::iterator_category
>, BidirIterators
>
{
return std::prev(it, n);
}

如果没有,在C++标准库中拥有不是很好吗?

不。

如果迭代器提供编号递增和递减操作,则它表明此类操作是快速的,最好是摊销的 O(1( 操作。这就是为什么你希望人们拼出std::next/prev:这样编写/阅读代码的人就完全清楚,有问题的操作可能会很慢。也就是说,即使用户传递了一个可以快速完成的迭代器,您的算法也会识别出用户可能会传递一个可能很慢的迭代器。

在双向或更小的迭代器上提供这样的运算符就是说谎。这也是为什么你不应该把这样的运算符强加给任意迭代器。

但是你的代码是错误的还有其他原因。

纯输入迭代器(这可能是您所期望的,因为您调用了模板参数InputIt(不需要递减,因此std::prev无效。它必须是一个双向迭代器。

但还有更多。可以复制前向迭代器,各种副本被视为范围内的独立位置。纯输入迭代器不是这种情况;如果复制它们,则假定递增一个副本会使该迭代器的所有其他副本无效。因此,用户在尝试使用它时必须知道这一点,并注意不要假设单独的迭代器指向不同的位置。

但这不是指针通常的工作方式,是吗?所以再一次,你的迭代器似乎在对用户撒谎:它声称提供它没有的功能。