на основе диапазона для цикла для значений частной карты - PullRequest
0 голосов
/ 05 июля 2018

У меня есть следующий код:

#include "stdafx.h"
#include <map>
#include <string>
#include <iostream>

class MyObject
{
public:
    MyObject()
        : m_Items{ { 1, "one" },{ 2, "two" },{ 3, "three" } }
    {}

    RETURNTYPE GetStringIterator() const
    {
        IMPLEMENTATION
    }

private:
    std::map<int, std::string> m_Items;
};


int main()
{
    MyObject o;
    for (auto& s : o.GetStringIterator())
    {
        std::cout << s;
    }
}

Что должно быть RETURNTYPE и IMPLEMENTATION, чтобы позволить любому клиенту MyObject (в данном случае функции main()) выполнять итерацию по значениям карты m_Items без копирования каких-либо данных. ? Похоже, это должно быть возможно с помощью диапазона c ++ 11 для циклов и итераторов. но я не смог понять, как.

Ответы [ 3 ]

0 голосов
/ 05 июля 2018

итерация на основе диапазона может быть достигнута следующим образом:

class MyObject
{
public:
    MyObject()
        : m_Items{ { 1, "one" },{ 2, "two" },{ 3, "three" } }
    {}

    auto begin()       { return m_Items.begin(); }
    auto begin() const { return m_Items.begin(); }
    auto end()       { return m_Items.end(); }
    auto end() const { return m_Items.end(); }

private:
    std::map<int, std::string> m_Items;
};

Копирование или не копирование значения зависит от того, как код написан на сайте вызова:

MyObject a;
for(auto [key,value] : a) {} // copies are made
for(auto & [key,value] : a) {} // no copy
for(auto const & [key,value] : a) {} // no copy

И вы можете отключить изменение значений карты, удалив неконстантные версии begin и end:

class MyObject
{
public:
    MyObject()
        : m_Items{ { 1, "one" },{ 2, "two" },{ 3, "three" } }
    {}

    auto begin() const { return m_Items.begin(); }
    auto end() const { return m_Items.end(); }

private:
    std::map<int, std::string> m_Items;
};

Тогда попытки изменить значение в цикле range-for приведут к ошибке компиляции:

MyObject a;
for(auto & [key,value] : a) {
    //value.push_back('a');      // Not OK 
}
for(auto & [key,value] : a) {
    cout << value;             // OK
}

Обратите внимание, что если карта - это деталь реализации, следует использовать ответ, предложенный @Barry, поскольку он повторяется только для значений карты, а не для ключей.

0 голосов
/ 05 июля 2018

Я собираюсь сначала ответить на это в .

Вот минимальный итератор отображения:

template<class F, class It>
struct iterator_mapped {
  decltype(auto) operator*() const {
    return f(*it);
  }

  iterator_mapped( F f_in, It it_in ):
    f(std::move(f_in)),
    it(std::move(it_in))
  {}

  iterator_mapped( iterator_mapped const& ) = default;
  iterator_mapped( iterator_mapped && ) = default;
  iterator_mapped& operator=( iterator_mapped const& ) = default;
  iterator_mapped& operator=( iterator_mapped && ) = default;

  iterator_mapped& operator++() {
    ++it;
    return *this;
  }
  iterator_mapped operator++(int) {
    auto copy = *this;
    ++*this;
    return copy;
  }
  friend bool operator==( iterator_mapped const& lhs, iterator_mapped const& rhs ) {
    return lhs.it == rhs.it;
  }
  friend bool operator!=( iterator_mapped const& lhs, iterator_mapped const& rhs ) {
    return !(lhs==rhs);
  }
private:
  F f;
  It it;
};

технически это не итератор, но он подходит для for(:) циклов.

template<class It>
struct range_t {
  It b, e;
  It begin() const { return b; }
  It end() const { return e; }
};
template<class It>
range_t<It> range( It b, It e ) {
  return {std::move(b), std::move(e)};
}

Выше приведен абсолютно минимальный тип диапазона итератора, который может быть for(:) повторен.

template<class F, class R>
auto map_range( F&& f, R& r ) {
  using std::begin; using std::end;
  auto b = begin(r);
  auto e = end(r);
  using it = iterator_mapped<std::decay_t<F>, decltype(b)>;
  return range( it( f, b ), it( f, e ) );
}

обратите внимание, что R& не R&&; принимать значение для r здесь опасно.

auto GetStringIterator() const
{
  return map_range( [](auto&& pair)->decltype(auto){
    return pair.second;
  }, m_Items );
}

и готово.

Преобразование этого в - это боль. Вы должны бросить около 1026 * s вместо лямбды (или написать функциональные объекты, которые выполняют задачу вместо лямбды), заменить decltype(auto) на auto и конечные типы возврата, дать точный тип auto&& аргументов к лямбдам и т. д. В итоге у вас появляется на 25% -50% больше кода, большая часть которого скрывает погоню за типом.

Это в основном то, что делает boost::adaptors::map_values, но это происходит вручную, так что вы можете понять, как это работает, и не иметь буст-зависимости.

0 голосов
/ 05 июля 2018

Вы можете использовать boost::adaptors::map_values, это работает на C ++ 11:

auto GetStringIterator() const
    // NB: have the move the declaration of m_Items ahead of this function for this to work
    -> decltype(m_Items | boost::adaptors::map_values)
{
    return m_Items | boost::adaptors::map_values;
}

Или его эквивалент диапазона-v3, view::values. И то, и другое можно использовать как values(m) вместо m | values, если вам так больше нравится.

Любое решение возвращает представление к значениям карты. Это объект, который не владеет ни одним из своих базовых элементов и является дешевым для копирования - то есть O (1). Мы не копируем карту или любые ее элементы.

Вы бы использовали это, как если бы это был любой другой диапазон:

for (std::string const& s : o.GetStringIterator()) {
    // ...
}

Этот цикл не копирует строки. Каждый s относится непосредственно к соответствующему string, который хранит map.

...