如何为back_insert_iterator实现end sentine?

时间:2016-10-21 17:13:24

标签: c++ algorithm stl iterator idiomatic

我想用迭代器的连续值填充容器到另一个容器的元素(经常出现的现实问题),比如说:

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>

3 个答案:

答案 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或就地构造来解决缺少默认构造函数的问题

Full test program for C++20

答案 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++;}); 的另一种解决方案:

live

{{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::iotamiota使用此类迭代器。

完整的例子在这里:

#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;
}