向双向迭代器提供 operator+ 或 operator- 有什么缺点吗?
Is there any drawbacks in providing operator+ or operator- to bidirectional iterators?
bidirectional iterators have no luxuries like random access iterators, and hence need to depend upon the std::next
and std::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));
但是我们can implement thoseoperator+
和operator-
使用上面的std::next
和std::prev
.
#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++ 标准库不是很好吗?
更新:
使用 InputIt
是一个错误,因为 mentioned in his answer. I thought to demonstrate the idea though. Anyways, let us consider a SFINE ed solution instead 只接受双向迭代器。除了 Nicol 提到的问题,还会有其他问题吗?
#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);
}
If not, isn't nice to have in the C++ standard library?
没有
如果迭代器提供编号的递增和递减操作,则表明此类操作是 快速,最好是分期 O(1) 操作。这就是为什么您希望人们拼出 std::next/prev
:这样对于某些人 writing/reading 代码来说,有问题的操作可能很慢是完全显而易见的。也就是说,即使用户传递了一个可以快速执行的迭代器,您的算法也会识别出用户可能传递了一个可能会很慢的迭代器。
在双向或更小的迭代器上提供此类运算符是在撒谎。这也是为什么您不应将此类运算符强加于任意迭代器的原因。
但是还有其他原因导致您的代码错误。
纯输入迭代器(这可能是您所期望的,因为您调用了模板参数 InputIt
)不需要递减,因此 std::prev
无效。它必须是双向迭代器。
但还有更多。前向迭代器可以被复制,不同的副本被认为是一个范围内的独立位置。纯输入迭代器不是这种情况;如果您复制它们,则假设递增一个副本会使该迭代器的所有其他副本无效。因此,用户在尝试使用它时必须知道这一点,并注意不要假设单独的迭代器指向不同的位置。
但是指针通常不是这样工作的,是吗?所以再一次,您的迭代器似乎在欺骗用户:它声称 提供它没有提供的功能。
bidirectional iterators have no luxuries like random access iterators, and hence need to depend upon the std::next
and std::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));
但是我们can implement thoseoperator+
和operator-
使用上面的std::next
和std::prev
.
#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++ 标准库不是很好吗?
更新:
使用 InputIt
是一个错误,因为
#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);
}
If not, isn't nice to have in the C++ standard library?
没有
如果迭代器提供编号的递增和递减操作,则表明此类操作是 快速,最好是分期 O(1) 操作。这就是为什么您希望人们拼出 std::next/prev
:这样对于某些人 writing/reading 代码来说,有问题的操作可能很慢是完全显而易见的。也就是说,即使用户传递了一个可以快速执行的迭代器,您的算法也会识别出用户可能传递了一个可能会很慢的迭代器。
在双向或更小的迭代器上提供此类运算符是在撒谎。这也是为什么您不应将此类运算符强加于任意迭代器的原因。
但是还有其他原因导致您的代码错误。
纯输入迭代器(这可能是您所期望的,因为您调用了模板参数 InputIt
)不需要递减,因此 std::prev
无效。它必须是双向迭代器。
但还有更多。前向迭代器可以被复制,不同的副本被认为是一个范围内的独立位置。纯输入迭代器不是这种情况;如果您复制它们,则假设递增一个副本会使该迭代器的所有其他副本无效。因此,用户在尝试使用它时必须知道这一点,并注意不要假设单独的迭代器指向不同的位置。
但是指针通常不是这样工作的,是吗?所以再一次,您的迭代器似乎在欺骗用户:它声称 提供它没有提供的功能。