我想用迭代器的连续值填充容器到另一个容器的元素(经常出现的现实问题),比如说:
std::container1< T > c1{/* initialized */};
assert(!c1.empty());
std::continer2< typename std::container1< T >::iterator > c2;
auto it = std::begin(c1), const end = std::end(c1);
do { c2.push_back(it); } while (++it != end);
STL中有一个有吸引力的std::iota
算法,但它是基于范围的,对于std::back_inserter(c2)
,目前无法达到预期目标。但是在STL的下一个版本中,我可以期待以下形式的iota
算法:
template< typename ForwardIterator, typename EndSentinel, typename T >
void
iota(ForwardIterator first, EndSentinel last, T value)
{
for (; first != last; ++first) {
*first = value;
++value;
}
}
在EndSentinel
operator != (ForwardIterator, EndSentinel)
循环的iota
步骤完成后,如何实施c1.size()
和for
以使iota(std::back_inserter(c1), something(c1, c1.size()), std::begin(c1))
以上停止?< / p>
答案 0 :(得分:2)
std::back_insert_iterator
(或任何OutputIterator)没有哨兵,也没有相等运算符,因为输出迭代器是“无限序列”:您可以将元素附加到容器的末尾或写入文件,直到内存或磁盘空间用完。
但是,如果需要调用期望“输出前哨信号”的算法,则有一个带有输出前哨信号的输出迭代器是有意义的(因为如果输出是“有限序列”,例如,不期望这样会不安全,例如预分配的std::vector
)。这样的算法看起来像:
template<typename InIter, typename InSentinel, typename OutIter, typename OutSentinel>
OutIter modernAlgorithm(InIter first, InSentinel last, OutIter outFirst, OutSentinel outLast);
在这种情况下,您只需要一个琐碎的前哨,即可将不平等与一切相提并论。另请参见this answer。
template<typename T>
struct TrivialSentinel
{
bool operator==(const T&) { return false; }
bool operator!=(const T&) { return true; }
friend bool operator==(const T&, TrivialSentinel&) { return false; }
friend bool operator!=(const T&, TrivialSentinel&) { return true; }
};
modernAlgorithm(v.begin(), v.end(), std::back_inserter(r), TrivialSentinel<decltype(std::back_inserter(r))>());
(这可能看起来很奇怪,但是如果您考虑到即使对相同的*out = expr
值重复执行相同的操作out
,输出也会处于不同的状态,这是有道理的时间,因此从某种意义上讲,没有两个输出迭代器一定是等效的...)
但是,较旧的算法通常不允许迭代器和哨兵具有不同的类型:
template<typename InIter, typename OutIter>
OutIter olderAlgorithm(InIter first, InIter last, OutIter outFirst, OutIter outLast);
在这种情况下,您可以编写std::back_insert_iterator
的子类或包装,该子类或包装具有默认构造函数,并且始终将自身与不相等进行比较。
这在C ++ 20中很容易,其中std::back_insert_iterator
有一个default constructor:
// C++20
template<typename C>
struct BackInsertIteratorWithSentinel : public std::back_insert_iterator<C>
{
BackInsertIteratorWithSentinel() {} // C++20 only
BackInsertIteratorWithSentinel(C& c) : std::back_insert_iterator<C>(c) {}
bool operator==(const BackInsertIteratorWithSentinel&) { return false; }
bool operator!=(const BackInsertIteratorWithSentinel&) { return true; }
};
template<typename C>
BackInsertIteratorWithSentinel<C> BackInserterWithSentinel(C& c)
{
return BackInsertIteratorWithSentinel<C>(c);
}
template<typename C>
BackInsertIteratorWithSentinel<C> BackInserterWithSentinel()
{
return BackInsertIteratorWithSentinel<C>();
}
olderAlgorithm(v.begin(), v.end(), BackInserterWithSentinel(r), BackInserterWithSentinel<std::vector<int> >());
请注意,即使在C ++ 20中,std::back_insert_iterator
也没有相等运算符。
如果必须支持旧版本的C ++,则可能必须从头开始实现自己的std::back_insert_iterator
,或使用boost::optional
或就地构造来解决缺少默认构造函数的问题
答案 1 :(得分:1)
我认为你不能这样做 - 或者我不明白你的问题,但是......
根据http://en.cppreference.com/w/cpp/algorithm/iota,该算法适用于现有的元素范围 - 因此将它与generate_n
一起用作第一个迭代器是没有意义的,它首先用于插入元素。
我想用迭代器的连续值填充容器到另一个容器的元素
使用 std::vector<int> src = {0,1,2,3};
std::vector<std::vector<int>::iterator> dst;
std::generate_n(std::back_inserter(dst), src.size(), [it=src.begin()]() mutable {return it++;});
的另一种解决方案:
{{1}}
答案 2 :(得分:1)
您的问题包括iota
实施,与我认为的标准不同。这是我所知道的标准版http://en.cppreference.com/w/cpp/algorithm/iota。
您的iota
(我会在代码中将其重命名为miota
)允许使用不同类型的迭代器来开始和结束。
算法中你想要的是; end sentinel需要与begin(插入器)不同,直到处理完所有值。对于处理值,您只需要一个对象,并在该对象上使用递增和复制构造。
因此,你的结束哨兵应该知道价值处理,一旦结束,哨兵应该以某种方式等于插入者。
我是通过在名为IotaHelper
的类中保存原始容器的begin / end迭代器来完成的。这使用shared_ptr
与sentinel类共享状态,该类称为IotaEndSentinel
。
当您在value
内增加miota
时,它实际上会增加IotaHelper
的开始迭代器。当你检查与插入器和sentinel的相等性时,它实际上检查IotaHelper
内的迭代器相等性。
所有带有基本示例的代码都在这里:
#include <iterator>
#include <numeric>
#include <vector>
#include <iostream>
#include <utility>
#include <memory>
template< typename ForwardIterator, typename EndSentinel, typename T >
void miota(ForwardIterator first, EndSentinel last, T value)
{
for (; first != last; ++first) {
*first = value;
++value;
}
}
template<typename Container>
struct IotaHelper
{
using Iterator = typename Container::iterator;
using IteratorPair = std::pair<Iterator, Iterator>;
IotaHelper(Iterator begin, Iterator end)
:
pair(std::make_shared<IteratorPair>(begin, end))
{ }
operator Iterator()
{
return pair->first;
}
IotaHelper& operator++()
{
++pair->first;
return *this;
}
std::shared_ptr<IteratorPair> pair;
};
template<typename Container>
struct IotaEndSentinel
{
using Helper = IotaHelper<Container>;
using Iterator = typename Helper::Iterator;
IotaEndSentinel(const Helper& helper)
:
helper(helper)
{}
template<typename C>
friend bool operator!=(const std::back_insert_iterator<C>& bii,
const IotaEndSentinel& sentinel)
{
return sentinel.helper.pair->first != sentinel.helper.pair->second;
}
Helper helper;
};
int main()
{
using Container0 = std::vector<int>;
using Container1 = std::vector<Container0::iterator>;
Container0 c0 = {1, 2, 3, 4, 5};
Container1 c1;
IotaHelper<Container0> iotaHelper(c0.begin(), c0.end());
miota(std::back_inserter(c1),
IotaEndSentinel<Container0>(iotaHelper),
iotaHelper);
std::cout << "Result: ";
for (auto iter : c1)
{
std::cout << *iter << ", ";
}
std::cout << std::endl;
}
我试图这样做因为它很有趣。但请不要使用此方法来破解back_insert_iterator
之类的输出迭代器,并为不同的容器自己创建一个通用方法。
template<typename SourceContainer, typename IteratorContainer>
void FillIterators(SourceContainer& sc, IteratorContainer& ic)
{
for (auto iter = sc.begin(); iter != sc.end(); ++iter)
{
ic.insert(ic.end(), iter);
}
}
修改强>
使用堆分配后,代码闻到了我的味道。我们可以推断“迭代器和过程”,而不是试图推断“价值和过程”。
我们可以构建一个迭代器包装器,它包含进程迭代器和插入迭代器。
当算法需要取消引用包装器时,它将返回插入迭代器。
当算法需要与其他“wrapper or sentinel”进行比较时,包装器将比较进程迭代器。
最后,我们可以为std::iota
和miota
使用此类迭代器。
完整的例子在这里:
#include <iterator>
#include <numeric>
#include <vector>
#include <iostream>
#include <utility>
#include <memory>
template< typename ForwardIterator, typename EndSentinel, typename T >
void miota(ForwardIterator first, EndSentinel last, T value)
{
for (; first != last; ++first) {
*first = value;
++value;
}
}
template<typename InsertIterator, typename Iterator>
struct InsertWrapper
{
InsertWrapper(const InsertIterator& inserter, const Iterator& iter)
:
inserter(inserter),
iter(iter)
{ }
bool operator!=(const InsertWrapper& other) const
{
//only compare process iterators
return iter != other.iter;
}
bool operator!=(const Iterator& sentinel) const
{
//compare process iterator against the sentinel
return iter != sentinel;
}
InsertIterator& operator*()
{
//return inserter for dereference
return inserter;
}
InsertWrapper& operator++()
{
//iterate inserter as the process progresses
++inserter;
++iter;
return *this;
}
InsertIterator inserter;
Iterator iter;
};
template<typename InsertIterator, typename Iterator>
InsertWrapper<InsertIterator, Iterator> WrapInserter(const InsertIterator& inserter,
const Iterator& iter)
{
return InsertWrapper<InsertIterator, Iterator>(inserter, iter);
}
int main()
{
using Container0 = std::vector<int>;
using Container1 = std::vector<Container0::iterator>;
Container0 c0 = {1, 2, 3, 4, 5};
Container1 c1;
//use wrapper as usual iterator begin/end
std::iota(WrapInserter(std::back_inserter(c1), c0.begin()),
WrapInserter(std::back_inserter(c1), c0.end()),
c0.begin());
std::cout << "std::iota result: ";
for (auto iter : c1)
{
std::cout << *iter << ", ";
}
std::cout << std::endl;
c1.clear();
miota(WrapInserter(std::back_inserter(c1), c0.begin()),
c0.end(), //end iterator as sentinel
c0.begin());
std::cout << "miota result: ";
for (auto iter : c1)
{
std::cout << *iter << ", ";
}
std::cout << std::endl;
}