簡體   English   中英

為不使用元組的自定義映射模擬 std::map 迭代器

[英]Emulate std::map iterators for custom map that does not use tuples

我想創建一個自定義映射,它實際上使用一組固定的鍵,但應該表現得像std::map 基本上我在內部使用一個數組並將鍵映射到索引,從而允許非常有效的查找。 然而,我正在努力實現行為類似於std::map迭代器的迭代器,因為我沒有可以std::pair引用的內部std::pair

是否可以在保留std::map接口(尤其是迭代器)的同時將其實現為零開銷抽象?

我能想到的最好的operator*是返回一個右值std::pair<key_type, mapped_type*> ,它基本上允許相同的操作,但不幸的是使用模式不同。

我也試過std::pair<key_type, boost::referene_wrapper<mapped_type>> ,但仍然不允許for(auto& elem : map)並且經常需要elem.second.get()原因我不明白.

如果對用例有任何幫助,我很樂意使用 boost 或輕量級頭庫。

為了說明這種情況,下面是一個包含所有字母“a”-“z”的地圖的最小示例。

using letter = char;
static const letter letter_begin = 'a';
static const letter letter_end = 'z' + 1;

template <typename T>
class letter_map
{
private:
    using self = letter_map<T>;

    template <typename IT, typename M>
    class iterator_base : public std::iterator<std::input_iterator_tag, T>
    {
    public:
        iterator_base(letter index, M& map) : index_(index), data_(map)
        {
        }

        using self_iter = iterator_base<IT, M>;
        IT operator*()
        {
            return IT(index_, &data_[index_]);
        }

        self_iter& operator++()
        {
            index_++;
            return *this;
        }

        self_iter operator++(int)
        {
            self_iter tmp(*this);
            operator++();
            return tmp;
        }

        bool operator==(self_iter other) const
        {
            assert(&data_ == &other.data_);
            return index_ == other.index_;
        }

        bool operator!=(self_iter other) const
        {
            return !(*this == other);
        }

    private:
        letter index_;
        M& data_;
    };

public:
    using key_type = letter;
    using mapped_type = T;
    using value_type = std::pair<const key_type, mapped_type*>;
    using const_value_type = std::pair<const key_type, const mapped_type*>;

private:
    static const size_t data_size = letter_end - letter_begin;
    using container_type = std::array<mapped_type, data_size>;

public:
    using iterator = iterator_base<value_type, self>;
    using const_iterator = iterator_base<const_value_type, const self>;

public:
    mapped_type& operator[](letter l)
    {
        return data_[l - letter_begin];
    }

    const mapped_type& operator[](letter l) const
    {
        return data_[l - letter_begin];
    }

    auto begin()
    {
        return iterator(letter_begin, *this);
    }

    auto end()
    {
        return iterator(letter_end, *this);
    }

    auto begin() const
    {
        return const_iterator(letter_begin, *this);
    }

    auto end() const
    {
        return const_iterator(letter_end, *this);
    }

private:
    container_type data_;
};

void print_string_letter_map(const letter_map<std::string>& lm)
{
    for (auto elem : lm)
    {
        std::cout << elem.first << "->" << *(elem.second) << std::endl;
    }
}

template<typename T>
class std_letter_map : public std::map<letter, T>
{
public:
    std_letter_map()
    {
        for (letter l = letter_begin; l != letter_end; ++l) {
            this->emplace(l, T());
        }
    }
};

void print_string_std_letter_map(const std_letter_map<std::string>& lm)
{
    for (const auto& elem : lm)
    {
        std::cout << elem.first << "->" << elem.second << std::endl;
    }
}

int main()
{
    letter_map<std::string> lm;
    // usually I would use auto& elem here
    for (auto elem : lm) {
        auto let = elem.first;
        // usually this would be without the *
        auto& str = *(elem.second);
        str = std::string("foo ") + let;
    }
    print_string_letter_map(lm);
    return 0;
}

實現operator *很容易 - 只需為 const 迭代器返回std::pair<const Key, Value&>..., Value const&> ,就像在這個簡化的例子中一樣:

template <typename T>
class iterator_array_as_map
{
public:

    iterator_array_as_map(T* array, int index) 
       : array(array), index(index) 
    {}

    bool operator == (const iterator_array_as_map& other) const
    {
        return index == other.index;
    }
    bool operator != (const iterator_array_as_map& other) const
    {
        return index != other.index;
    }
    iterator_array_as_map& operator ++ ()
    {
        ++index;
        return *this;
    }

    auto operator * ()
    {
        return std::pair<const int, T&>(index, array[index]);
    }


private:
    T* array;
    int index;
};

和用法:

int main() {
    int array[2] = {2, 4};
    auto begin = iterator_array_as_map<int>(array, 0);
    auto end = iterator_array_as_map<int>(array, 2);

    for (auto it = begin; it != end; ++it)
    {
        std::cout << (*it).first << " " << (*it).second << std::endl;
     }

    (*begin).second = 7;

    for (auto it = begin; it != end; ++it)
    {
        std::cout << (*it).first << " " << (*it).second << std::endl;
    }
}

operator ->有點難 - 因為你必須返回一些需要模擬pointer to std::pair - 但如果不介意動態內存碎片 - 你可以只返回std::shared_ptr<std::pair<....>> ...

    auto operator -> ()
    {
        return std::make_shared<std::pair<const int, T&>>(index, array[index]);
    }

如果您不想使用動態內存 - 那么您可以嘗試使用指向自身解決方案的指針:

template<typename data>    
class pointer_to_data
{
public:
    template<typename ...Arg>
    pointer_to_data(Arg&&... arg)
      : data{std::forward<Arg>(arg)...}
    {}
    Data* operator -> ()
    {
        return &data;
     }
  private:
      Data data;
  };

只需返回上述內容,而不是 shared_ptr ...


請參閱此what-is-the-correct-way-of-using-c11s-range-based-for部分“代理迭代器的特殊情況”。 如果bstd::vector<bool> ,則無法定義for(auto&e:b) - 因為通常不可能引用臨時容器,並且這個容器在這個意義上與您的容器相似具有“特殊”引用類型。

您可以嘗試在迭代器中使用特殊成員來保持“返回值”——但這會很麻煩——所以我提出的解決方案可能不會更好。

您可能可以使用 Eric Niebler 的范圍庫中的 zip_view https://github.com/ericniebler/range-v3

暫無
暫無

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

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