Подтвердить что ты не робот

Возможно ли в С++ выполнить std:: map <> "для итерации element: container" с именованными переменными (например, ключ и значение) вместо .first и .second?

Я не был уверен, что искать. Я нашел переименование первого и второго итератора карт, но это не совсем то, что я хочу сделать.

Вот что я хотел бы сделать [см. ниже для бессмысленного кода на С++]. Что-то близко к этому возможно? В противном случае просто нужно пойти с возможностью "адаптировать" итератор как первую строку внутри цикла, я полагаю.

// what I want to do:
std::map<int, std::string> my_map;
// ... populate my_map
for(auto key, auto & value: my_map){
    // do something with integer key and string value
}

С++ 11 хорош, но скорее избегайте повышения, если это возможно.

Ближайший я получил

// TODO, can this be templated?
struct KeyVal{
    int & id;
    std::string & info;

    template <typename P>
    KeyVal(P & p)
        : id(p.first)
        , info(p.second)
    {
    }
};

//...
for ( KeyVal kv : my_map ){
    std::cout << kv.info;
}

Но это означает запись класса адаптера для каждого отображения: (

// slightly joke answer/"what could possibly go wrong?"
#define key first
#define value second
4b9b3361

Ответ 1

Подход, основанный на Барри ниже, заключался бы в написании адаптера диапазона.

Выполнение этого без boost или аналогичной поддержки библиотеки - это боль, но:

  • Напишите шаблон диапазона. Он хранит 2 class iterator и имеет методы begin() и end() (и все, что вам нужно).

  • Напишите преобразовательный адаптер итератора. Он принимает итератор и завершает его таким образом, что его тип значения преобразуется каким-то функциональным объектом F.

  • Напишите трансформатор to_kv, который принимает std::pair<K, V> cv& и возвращает struct kv_t { K cv& key; V cv& value; }.

  • Провод 3 на 2 в 1 и назовите его as_kv. Он принимает диапазон пар и возвращает диапазон значений ключа.

Синтаксис, в котором вы закончите:

std::map<int, std::string> m;

for (auto kv : as_kv(m)) {
  std::cout << kv.key << "->" << kv.value << "\n";
}

что приятно.

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

template<class Key, class Value>
struct kv_t {
  Key&& key;
  Value&& value;
};

// not a true iterator, but good enough for for(:)
template<class Key, class Value, class It>
struct kv_adapter {
  It it;
  void operator++(){ ++it; }
  kv_t<Key const, Value> operator*() {
    return {it->first, it->second};
  }
  friend bool operator!=(kv_adapter const& lhs, kv_adapter const& rhs) {
    return lhs.it != rhs.it;
  }
};
template<class It, class Container>
struct range_trick_t {
  Container container;
  range_trick_t(Container&&c):
    container(std::forward<Container>(c))
  {}
  It begin() { return {container.begin()}; }
  It end() { return {container.end()}; }
};
template<class Map>
auto as_kv( Map&& m ) {
  using std::begin;
  using iterator = decltype(begin(m)); // no extra (())s
  using key_type = decltype((begin(m)->first)); // extra (())s on purpose
  using mapped_type = decltype((begin(m)->second)); // extra (())s on purpose
  using R=range_trick_t<
    kv_adapter<key_type, mapped_type, iterator>,
    Map
  >;
  return R{std::forward<Map>(m)};
}
std::map<int, std::string> m() { return {{0, "Hello"}, {2, "World"}}; }

который очень минимален, но работает. Я бы вообще не рекомендовал подобные наполовину псевдотераторы для циклов for(:); использование реальных итераторов - это лишь скромная дополнительная стоимость, и в дальнейшем это не удивляет.

живой пример

(теперь с поддержкой временной карты. Не поддерживает плоские массивы C... еще)

Трюк диапазона хранит контейнер (возможно, ссылку), чтобы копировать временные контейнеры в объект, хранящийся в течение цикла for(:). Не временные контейнеры типа Container являются Foo&, поэтому он не создает избыточную копию.

С другой стороны, kv_t, очевидно, хранит только ссылки. Может быть странный случай итераторов, возвращающих временные ряды, которые нарушают эту реализацию kv_t, но я не знаю, как избежать ее вообще, не жертвуя производительностью в более распространенных случаях.


Если вам не нравится часть kv. выше, мы можем сделать некоторые решения, но они не такие чистые.

template<class Map>
struct for_map_t {
  Map&& loop;
  template<class F>
  void operator->*(F&& f)&&{
    for (auto&& x:loop) {
      f( decltype(x)(x).first, decltype(x)(x).second );
    }
  }
};
template<class Map>
for_map_t<Map> map_for( Map&& map ) { return {std::forward<Map>(map)}; }

то

map_for(m)->*[&](auto key, auto& value) {
  std::cout << key << (value += " ") << '\n';
};

достаточно близко?

живой пример

Есть некоторые предложения в отношении первоклассных кортежей (и, следовательно, пар), которые могут дать вам что-то подобное, но я не знаю статус предложений.

Синтаксис, который вы можете завершить, если он попадет в С++, будет выглядеть примерно так:

for( auto [&&key, &&value] : container )

Комментарии к ->* мерзости выше:

Таким образом, ->* используется как operator bind из Haskell (вместе с неявной распаковкой), и мы кормляем его лямбдой, которая берет данные, содержащиеся в карте, и возвращает void. Возвращаемый тип (Haskell-esque) становится картой над void (ничего), которую я выхожу в пустоту.

У техники есть проблема: вы теряете break; и continue;, которые сосут.

В меньшем варианте hackey Haskell ожидается, что лямбда вернет что-то вроде void | std::experimental::expected<break_t|continue_t, T>, и если T is void ничего не возвращает, если T является корневым типом, возвращающим карту, и если T - это карта, соединяющая возвращаемый тип карты. Он также распаковывал или не распаковывал содержащийся кортеж в зависимости от того, что хочет лямбда (определение стиля SFINAE).

Но это немного для ответа SO; это отступление указывает, что вышеупомянутый стиль программирования не является полным тупиком. В С++ это нетрадиционно.

Ответ 2

Вы можете написать шаблон класса:

template <class K, class T>
struct MapElem {
    K const& key;
    T& value;

    MapElem(std::pair<K const, T>& pair)
        : key(pair.first)
        , value(pair.second)
    { }
};

с преимуществом возможности писать key и value, но с недостатком необходимости указывать типы:

for ( MapElem<int, std::string> kv : my_map ){
    std::cout << kv.key << " --> " << kv.value;
}

И это не сработает, если my_map были const. Вам нужно будет сделать что-то вроде:

template <class K, class T>
struct MapElem {
    K const& key;
    T& value;

    MapElem(std::pair<K const, T>& pair)
        : key(pair.first)
        , value(pair.second)
    { }

    MapElem(const std::pair<K const, std::remove_const_t<T>>& pair)
        : key(pair.first)
        , value(pair.second)
    { }
};

for ( MapElem<int, const std::string> kv : my_map ){
    std::cout << kv.key << " --> " << kv.value;
}

Это беспорядок. Самое лучшее на данный момент - просто привыкнуть писать .first и .second и надеяться, что предложение структурированных привязок пройдет, что позволит сделать то, что вы действительно хотите:

for (auto&& [key, value] : my_map) {
    std::cout << key << " --> " << value;
}

Ответ 3

Самое близкое, что нужно использовать std::tie:

std::map<int, std::string> my_map;
int key;
std::string value;
for(auto&& p: my_map)
{
    std::tie(key, value) = p;
    std::cout << key << ": " << value << std::endl;
}

Конечно, выражения не могут быть помещены в цикл for for, поэтому вместо этого можно использовать макрос для выражения:

#define FOREACH(var, cont) \
    for(auto && _p:cont) \
        if(bool _done = false) {} \
        else for(var = std::forward<decltype(_p)>(_p); !_done; _done = true)

Итак, тогда std::tie можно использовать непосредственно в цикле:

std::map<int, std::string> my_map;
int key;
std::string value;
FOREACH(std::tie(key, value), my_map)
{
    std::cout << key << ": " << value << std::endl;
}

Ответ 4

Просто для того, чтобы предоставить еще один способ почти сделать то, что вы хотите, я написал это некоторое время назад, чтобы избежать использования .first и .second по всему моему коду:

auto pair2params = [](auto&& f)
{
    return [f](auto&& p) {
        f(p.first, p.second);
    };
};

Теперь вы можете написать что-то вроде (при использовании for_each на основе диапазона):

int main()
{
    auto values = map<int, string>{
        {0, "hello"},
        {1, "world!"}
    };

    for_each(values, pair2params([](int key, const string& value) {
        cout << key << ": " << value << "\n";
    });
}

Пример выполнения: http://ideone.com/Bs9Ctm

Ответ 5

Я обычно предпочитаю подход KISS для этого:

template<typename KeyValuePair>
typename KeyValuePair::first_type& key(KeyValuePair& kvp)
{
    return kvp.first;
}

template<typename KeyValuePair>
const typename KeyValuePair::first_type& key(const KeyValuePair& kvp)
{
    return kvp.first;
}

template<typename KeyValuePair>
void key(const KeyValuePair&& kvp) = delete;

template<typename KeyValuePair>
typename KeyValuePair::second_type& value(KeyValuePair& kvp)
{
    return kvp.second;
}

template<typename KeyValuePair>
const typename KeyValuePair::second_type& value(const KeyValuePair& kvp)
{
    return kvp.second;
}

template<typename KeyValuePair>
void value(const KeyValuePair&& kvp) = delete;

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

for(auto& kvp : my_map) {
    std::cout << key(kvp) << " " << value(kvp) << "\n";
}

Ответ 6

В Apache Mesos мы используем макрос с именем foreachpair, который можно использовать следующим образом:

foreachpair (const Key& key, const Value& value, elems) {
  /* ... */
}

Вы можете, конечно, заменить Key и Value на auto вместе с любыми квалификаторами, которые вы хотели бы использовать там. Он также поддерживает break и continue.

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

#define FOREACH_PREFIX   BOOST_PP_CAT(foreach_, __LINE__)

#define FOREACH_BODY     BOOST_PP_CAT(FOREACH_PREFIX, _body__)
#define FOREACH_BREAK    BOOST_PP_CAT(FOREACH_PREFIX, _break__)
#define FOREACH_CONTINUE BOOST_PP_CAT(FOREACH_PREFIX, _continue__)
#define FOREACH_ELEM     BOOST_PP_CAT(FOREACH_PREFIX, _elem__)
#define FOREACH_ONCE     BOOST_PP_CAT(FOREACH_PREFIX, _once__)

Приведенные выше макросы предоставляют уникальные имена для различных компонентов, которые используются в макросе foreachpair, включая номер __LINE__.

 1 #define foreachpair(KEY, VALUE, ELEMS)                                     \
 2   for (auto&& FOREACH_ELEM : ELEMS)                                        \
 3     if (false) FOREACH_BREAK: break; /* set up the break path */           \
 4     else if (bool FOREACH_CONTINUE = false) {} /* var decl */              \
 5     else if (true) goto FOREACH_BODY; /* skip the loop exit checks */      \
 6     else for (;;) /* determine whether we should break or continue. */     \
 7       if (!FOREACH_CONTINUE) goto FOREACH_BREAK; /* break */               \
 8       else if (true) break; /* continue */                                 \
 9       else                                                                 \
10         FOREACH_BODY:                                                      \
11         if (bool FOREACH_ONCE = false) {} /* var decl */                   \
12         else for (KEY = std::get<0>(                                       \
13                       std::forward<decltype(FOREACH_ELEM)>(FOREACH_ELEM)); \
14                   !FOREACH_ONCE; FOREACH_ONCE = true)                      \
15           for (VALUE = std::get<1>(                                        \
16                    std::forward<decltype(FOREACH_ELEM)>(FOREACH_ELEM));    \
17                !FOREACH_CONTINUE; FOREACH_CONTINUE = true)

Я пройду по этой линии.

  • (Начало беспорядка).
  • Итерация по диапазону на основе цикла ELEMS.
  • Мы установили ярлык FOREACH_BREAK. Мы переходим к этой метке до break из этого цикла.
  • Мы установили флаг потока управления FOREACH_CONTINUE. Это true, если текущая итерация завершилась нормально или через continue и false, если текущая итерация прошла через break.
  • Мы всегда переходим к метке FOREACH_BODY ниже.
  • Здесь мы перехватываем поток управления и проверяем флаг FOREACH_CONTINUE, чтобы определить, как мы вышли из текущей итерации.
  • Если FOREACH_CONTINUE - false, мы знаем, что мы вышли через break, поэтому переходим к FOREACH_BREAK.
  • В противном случае FOREACH_CONTINUE является true, а мы break из цикла for (;;), который выводит нас на следующую итерацию.
  • (На полпути через беспорядок).
  • Мы всегда прыгаем сюда из (5).
  • Настроить FOREACH_ONCE, который используется только для выполнения цикла for, который объявляет Key ровно один раз.
  • Объявить Key.
  • Переместите элемент правильно.
  • Используйте FOREACH_ONCE, чтобы убедиться, что этот цикл выполняется ровно один раз.
  • Объявить Value.
  • Переместите элемент правильно.
  • Используйте FOREACH_CONTINUE, чтобы убедиться, что этот цикл выполняется ровно один раз, и указать, был ли цикл завершен через break или нет.

ПРИМЕЧАНИЕ. Использование std::get позволяет поддерживать std::tuple или std::array, выходящие из последовательности. например, std::vector<std::tuple<int, int>>

Идеальная демонстрация