对于从std::back_inserter()
返回的迭代器,有什么东西可以用作“结束”迭代器吗?
起初这似乎有点荒谬,但我有一个API:
template<typename InputIterator, typename OutputIterator>
void foo(
InputIterator input_begin,
InputIterator input_end,
OutputIterator output_begin,
OutputIterator output_end
);
foo
对输入序列执行一些操作,生成输出序列。 (foo
知道谁的长度,但可能与输入序列的长度相等或不同。)
获取output_end
参数是奇怪的部分:例如,std::copy
不执行此操作,并假设您不会将其传递给垃圾。 foo
用它来提供范围检查:如果你传递的范围太小,它会以防御性编程的名义抛出一个异常。 (而不是潜在地覆盖内存中的随机位。)
现在,我想要传递foo
一个后插件,特别是来自std::vector
的插件,它没有内存限制之外的限制。我仍然需要一个“结束”迭代器 - 在这种情况下,一些永远不会比较平等的东西。 (或者,如果我有一个std::vector
但是对长度有限制,或许有时可能比较相等?)
我该怎么做呢?我确实能够更改foo
的API - 最好不检查范围,而是提供另一种方法来获得所需的输出范围? (无论如何对于原始数组都是必需的,但是后插入器不需要进入矢量。)这看起来不太稳健,但我正在努力使“强大”(上面)工作。
答案 0 :(得分:5)
如果foo
正在检查以确保distance(output_begin, output_end)
足够大以包含结果,那么您可以将哪些用作“结束”迭代器? back_inserter
将元素添加到结尾;根据定义,distance
添加元素的位置与序列末尾之间的back_inserter
是0
。
我认为foo
与std::copy
类似的签名foo(InIt, InIt, OutIt)
是您的最佳选择。它实际上并不“不健全”。对于大多数算法,出于性能原因,您只想在调试版本中进行此类范围检查,并且一个像样的标准库实现(如Visual C ++标准库)已经在调试版本中提供了大量的范围检查。
或者,你可以创建一个back_inserting_foo(InIt, InIt, Container)
,虽然为此创建一个特殊情况会有点不寻常,并且会给函数用户带来更大的负担,以便知道他们需要为不同类型的迭代器使用哪个重载
答案 1 :(得分:3)
您可以通过执行安全检查来避免更改foo()
的API,方法是在每个元素写出之前检查curr_output_iter != output_end
(参见下文),而不是在开始时检查distance()
OutputIterator
检查James McNellis suggests。
这样做需要编写自己的“迭代器适配器”来提供“增强型”输出迭代器,可以测试它是否在有效范围的末尾。然后你可以正确地将模板类型名从SafeOutputIterator
更改为/* Metafunction for determining whether the range has a fixed endpoint.
* Assume all iterators are bounded except for OutputIterators. */
template <typename Tag>
struct helper {
static bool value = true;
};
template <>
struct helper<output_iterator_tag> {
static bool value = false;
};
template <typename It>
struct is_bounded {
static bool value = helper<typename iterator_traits<It>::iterator_category>::value;
};
/* Wraps an output iterator. */
template<typename It, bool BOUNDED = is_bounded<It>::value>
class safe_output_iterator {
typedef typename iterator_traits<It>::value_type value_type;
public:
explicit safe_output_iterator(It iter, size_t limit = 0) : iter_(iter), limit_(limit) {}
safe_output_iterator& operator++() { /* Preinc */
++iter_;
return *this;
}
safe_output_iterator operator++(int) { /* Postinc */
safe_output_iterator temp(*this);
++iter_;
return temp;
}
/* Trick: Deferencing returns the same object, so that operator=() works */
safe_output_iterator& operator*() {
return *this;
}
/* Will be called despite "dereferencing" this iterator object */
safe_output_iterator& operator=() {
/* Insert check for limit == 0 here if you want */
--limit_;
return *_iter;
}
/* Addition to the OutputIterator concept. */
bool operator==(safe_output_iterator const& x) {
return BOUNDED && limit_ == x.limit_;
}
bool operator!=(safe_output_iterator const& x) {
return !(*this == x);
}
private:
It iter_;
size_t limit_;
};
/* Helper function templates to avoid typing out long typenames */
/* Handle iterators */
template <typename It>
safe_output_iterator<It> soi_begin(It it, size_t limit = 0) {
return safe_output_iterator<It>(it, limit);
}
template <typename It>
safe_output_iterator<It> soi_end(It it, size_t limit = 0) {
return safe_output_iterator<It>(it, limit);
}
/* Handle STL containers like vector and list */
template <typename C>
safe_output_iterator<It> soi_begin_cont(C cont) {
return safe_output_iterator<typename C::iterator>(cont.begin(), cont.size());
}
template <typename C>
safe_output_iterator<It> soi_end_cont(C cont) {
/* Actually the iterator supplied (here cont.end()) is unimportant... */
return safe_output_iterator<typename C::iterator>(cont.end());
}
- 即使这只是作为文档,因为它是在C ++中无法实现的。我们区分“有界”和“无界”迭代器对:对于后者,没有两个迭代器会比较相等,实际上第二个迭代器除了它的类型之外永远不需要它。
vector<int> u(10, 42), v(ENOUGH_SPACE), w, x(ENOUGH_SPACE);
// Explicit iterator pair; bounded
foo(u.begin(), u.end(), soi_begin(v.begin(), ENOUGH_SPACE), soi_end(v));
// Explicit iterator pair; unbounded (2nd iterator ignored, but we need the type)
foo(u.begin(), u.end(), soi_begin(w.back_inserter()), soi_end(w.back_inserter()));
// Use whole container
foo(u.begin(), u.end(), soi_begin_cont(x), soi_end_cont(x));
您现在可以编写如下代码:
foo()
您可以在每次通过curr_output_iter != output_end
之前*curr_output_iter
检查safe_output_iterator::operator=()
,也可以在safe_output_iterator
中插入支票。后者似乎更好,因为你不能忘记每当一对soi_begin_cont()
传递给任意算法时执行检查(可能这与STL的调试版本的工作方式类似)。您还可以为固定大小的数组编写soi_end_cont()
和{{1}}的重载。
如果STL算法使用ranges而不是迭代器对,那么所有这些都不会那么麻烦 - 例如,单个函数模板可以返回跨越整个数组的适当范围。
答案 2 :(得分:1)
正如我在对j_random_hacker的回答的评论中提到的,如果修改算法使得传递给它的迭代器可以是不同的类型,例如,
template <typename InIt1, InIt2, OutIt1, OutIt2>
void foo(InIt1 in_begin, InIt2 in_end, OutIt1 out_begin, OutIt2 out_end) { }
然后你可以很容易地编写一个back_inserter_end
类来测试:
class back_inserter_end
: std::iterator<std::output_iterator_tag, void>
{ };
bool operator==(back_inserter_end, back_inserter_end) { return true; }
bool operator!=(back_inserter_end, back_inserter_end) { return false; }
template <typename Container>
bool operator==(const back_insert_iterator<Container>&, back_inserter_end) {
return false;
}
template <typename Container>
bool operator==(back_inserter_end, const back_insert_iterator<Container>&) {
return false;
}
template <typename Container>
bool operator!=(const back_insert_iterator<Container>&, back_inserter_end) {
return true;
}
template <typename Container>
bool operator!=(back_inserter_end, const back_insert_iterator<Container>&) {
return true;
}
然后,您可以调用“安全”算法:
foo(it, it, std::back_inserter(v), back_inserter_end());
在“安全”算法中,您可以在使用out_it == out_end
之前测试out_it
;由于out_it
是back_insert_iterator
,因此此测试将始终返回false
(这是所需行为)。