Как использовать основанный на диапазоне цикл for () с std :: map?

336

Типичный пример циклов for (), основанных на диапазоне C ++ 11, всегда выглядит примерно так:

std::vector<int> numbers = { 1, 2, 3, 4, 5, 6, 7 };
for ( auto xyz : numbers )
{
     std::cout << xyz << std::endl;
}

В каком случае xyzэто int. Но что происходит, когда у нас есть что-то вроде карты? Какой тип переменной в этом примере:

std::map< foo, bar > testing = { /*...blah...*/ };
for ( auto abc : testing )
{
    std::cout << abc << std::endl;         // ? should this give a foo? a bar?
    std::cout << abc->first << std::endl;  // ? or is abc an iterator?
}

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

Но я не понимаю, чего ожидать, когда речь идет о таких вещах, как карты и мультикарты.

(Я все еще на g ++ 4.4, хотя циклы на основе диапазона находятся в g ++ 4.6+, поэтому у меня еще не было возможности попробовать это).

Stéphane
источник
4
Диапазон для оператора делает нечестивый танец со стандартной библиотекой std::beginи std::endфункциями или функциями-членами под тем же именем.
Джин Бушуев
10
@Will В трехстрочном примере вы попадаете на поддельное имя переменной?
Стефан

Ответы:

495

Каждый элемент контейнера является a map<K, V>::value_type, который является typedefдля std::pair<const K, V>. Следовательно, в C ++ 17 или выше, вы можете написать

for (auto& [key, value]: myMap) {
    std::cout << key << " has value " << value << std::endl;
}

или как

for (const auto& [key, value]: myMap) {
    std::cout << key << " has value " << value << std::endl;
}

если вы не планируете изменять значения.

В C ++ 11 и C ++ 14 вы можете использовать расширенные forциклы для извлечения каждой пары самостоятельно, а затем вручную извлекать ключи и значения:

for (const auto& kv : myMap) {
    std::cout << kv.first << " has value " << kv.second << std::endl;
}

Вы могли бы также рассмотреть маркировку kvпеременной, constесли вы хотите просматривать значения только для чтения.

templatetypedef
источник
96

В C ++ 17 это называется структурированными привязками , что позволяет:

std::map< foo, bar > testing = { /*...blah...*/ };
for ( const auto& [ k, v ] : testing )
{
  std::cout << k << "=" << v << "\n";
}
напольная плитка
источник
Можно ли получить const &ключ, но неконстантную ссылку на значение? (потому что это то, что делает map :: value_type ...)
peterchen
2
@peterchen: kесть , constесли вы используетеfor(auto&[k,v]:testing)
Даль
1
cpppreference для структурированных привязок en.cppreference.com/w/cpp/language/structured_binding
TankorSmash
Если вы компилируете с GCC, вам нужна версия 7 или лучше для структурированных привязок: gcc.gnu.org/projects/cxx-status.html
csknk
25

Из этой статьи: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2049.pdf

for( type-specifier-seq simple-declarator : expression ) statement

синтаксически эквивалентно

{
    typedef decltype(expression) C;
    auto&& rng(expression);
    for (auto begin(std::For<C>::begin(rng)), end(std::For<C>::end(rng)); begin != end; ++ begin) {
        type-specier-seq simple-declarator(*begin);
        statement
    }
}

Таким образом, вы можете ясно видеть, что abcв вашем случае будет std::pair<key_type, value_type >. Таким образом, для печати вы можете получить доступ к каждому элементу abc.firstиabc.second

Аляска
источник
15

Если вы хотите видеть только ключи / значения на вашей карте и вам нравится использовать boost, вы можете использовать адаптеры boost с петлями на основе диапазона:

for (const auto& value : myMap | boost::adaptors::map_values)
{
    std::cout << value << std::endl;
}

есть эквивалентный boost :: adapters :: key_values

http://www.boost.org/doc/libs/1_51_0/libs/range/doc/html/range/reference/adaptors/reference/map_values.html

Пикси-Полуют
источник
3

Если оператор присваивания копии foo и bar дешевый (например, int, char, указатель и т. Д.), Вы можете сделать следующее:

foo f; bar b;
BOOST_FOREACH(boost::tie(f,b),testing)
{
  cout << "Foo is " << f << " Bar is " << b;
}
Балки
источник
4
Первый фрагмент кода не использует "C ++ 11 на основе диапазона для ()". Это не ответ на вопрос «C ++ 11: как использовать цикл for () для диапазона с std :: map?»
isoiphone
1
@ytj Уже упоминалось в ответе, что это не работает. Я не хочу удалять это, чтобы новым пользователям не приходилось пробовать это и узнавать факт снова.
балки