Возможно ли в С++ выполнить 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
Ответы
Ответ 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>>
Идеальная демонстрация