簡體   English   中英

如何為 back_insert_iterator 實現結束哨兵?

[英]How to implement end sentinel for back_insert_iterator?

我想通過迭代器的連續值填充一個容器到另一個容器的元素(經常發生現實生活中的問題),比如:

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

如何實現EndSentineloperator != (ForwardIterator, EndSentinel)以在iota(std::back_inserter(c1), something(c1, c1.size()), std::begin(c1))for循環的c1.size()步驟之后停止上面的iota iota(std::back_inserter(c1), something(c1, c1.size()), std::begin(c1)) ?

我不認為你能做到 - 或者我不明白你的問題,但是..

根據http://en.cppreference.com/w/cpp/algorithm/iota ,該算法適用於現有的元素范圍 - 因此將其與以下一起使用是沒有意義的: std::back_inserter作為第一個迭代器,它基本上是用於插入元素。

我想通過迭代器的連續值填充一個容器到另一個容器的元素

使用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++;});

您的問題包括一個iota實現,它與我相信的標准中的實現不同。 這是我知道的標准版本http://en.cppreference.com/w/cpp/algorithm/iota

您的iota (我將在我的代碼中將其重命名為miota )允許對開始和結束使用不同類型的迭代器。

你想要的算法是; end sentinel 需要與 begin (插入器)不同,直到處理完所有值。 對於處理值,您只需要一個對象,然后在該對象上使用增量和復制構造。

因此,您的結束哨兵應該知道值處理,並且完成后結束哨兵應該以某種方式與插入者相等。

我通過在一個名為IotaHelper的類中IotaHelper原始容器的開始/結束迭代器來做到這IotaHelper 這使用shared_ptr與稱為IotaEndSentinel的哨兵類共享狀態。

當您增加miotavalue ,它實際上增加了IotaHelper的開始迭代器。 當您使用插入器和哨兵檢查相等性時,它實際上會檢查IotaHelper的迭代器相等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);
    }
}

編輯:

使用堆分配后,我覺得這段代碼很糟糕。 與其試圖推理“價值和過程”,我們還可以推理“迭代器和過程”。

我們可以構建一個迭代器包裝器,其中包含進程迭代器和插入迭代器。

當算法需要取消引用包裝器時,它將返回插入迭代器。

當算法需要與其他“包裝器或哨兵”進行比較時,包裝器將比較進程迭代器。

最后,我們可以對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;
}

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);

在這種情況下,你需要的只是一個微不足道的哨兵,它比一切都不平等。 另請參閱此答案

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有一個默認構造函數

// 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或就地構造來解決缺少默認構造函數的問題。

C++20 的完整測試程序

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM