Идиоматический C++ для чтения из константной карты

Для std::map<std::string, std::string> variables я бы хотел сделать следующее:

BOOST_CHECK_EQUAL(variables["a"], "b");

Единственная проблема в том, что в этом контексте variables - это const, поэтому operator[] не будет работать :(

Теперь есть несколько обходных путей; отказавшись от const, используя variables.count("a") ? variables.find("a")->second : std::string() или даже создав функцию, оборачивающую его. Мне кажется, что все это не так хорошо, как operator[]. Что я должен делать? Есть стандартный способ сделать это (красиво)?

Редактировать: Просто чтобы сформулировать ответ, который никто из вас не хочет давать: нет, в C++ нет удобного, красивого, стандартного способа сделать это. Придется реализовать функцию поддержки.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
14
0
7 041
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

find - идиоматическая форма. Выбрасывать const - почти всегда плохая идея. Вы должны гарантировать, что операция записи не будет выполнена. Хотя этого можно разумно ожидать от доступа для чтения на карте, в спецификации об этом ничего не говорится.

Если вы знать, что значение существует, вы, конечно, можете отказаться от теста с использованием count (что в любом случае довольно неэффективно, поскольку это означает двойной обход карты. Даже если вы не знаете, существует ли элемент, я бы не стал его использовать. Вместо этого используйте следующее:

T const& item(map<TKey, T> const& m, TKey const& key, T const& def = T()) {
    map<TKey, T>::const_iterator i = m.find(key);
    return i == m.end() ? def : i->second;
}

/ EDIT: Как правильно указал Крис, построение по умолчанию объектов типа Tмощь будет дорогостоящим, особенно потому, что это делается, даже если этот объект на самом деле не нужен (поскольку запись существует). В этом случае не используйте значение по умолчанию для аргумента def в приведенном выше случае.

Ой! Это означает создание строки по умолчанию для каждого вызова, когда default (кстати, ключевое слово) не указан.

Chris Jester-Young 30.09.2008 15:42

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

Konrad Rudolph 30.09.2008 15:51

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

Chris Jester-Young 30.09.2008 15:53

Заметьте, мое предложение, приведенное выше, является излишним для вопроса. :-П

Chris Jester-Young 30.09.2008 15:53

Я думаю, что у вашего решения есть одно преимущество: вы можете вернуться с помощью T const & (полезно, если, например, T дорого копировать). Так что да, я тоже проголосовал за вашу запись (помимо большей общности). :-)

Chris Jester-Young 30.09.2008 16:01

Действительно, operator [] не является константой на std :: map, поскольку он автоматически вставляет пару ключ-значение в карту, если ее там не было. (Ооо, побочные эффекты!)

Правильный способ - использовать map::find и, если возвращенный итератор действителен (!= map.end()), возвращать second, как вы показали.

map<int, int> m;
m[1]=5; m[2]=6; // fill in some couples
...
map<int,int>::const_iterator it = m.find( 3 );
if ( it != m.end() ) {
    int value = it->second;
    // ... do stuff with value
}

Вы мог добавляете map::operator[]( const key_type& key ) const в подкласс используемой вами std :: map и утверждаете ключ, который нужно найти, после чего вы возвращаете it->second.

Ответ принят как подходящий

template <typename K, typename V>
V get(std::map<K, V> const& map, K const& key)
{
    std::map<K, V>::const_iterator iter(map.find(key));
    return iter != map.end() ? iter->second : V();
}

Улучшенная реализация на основе комментариев:

template <typename T>
typename T::mapped_type get(T const& map, typename T::key_type const& key)
{
    typename T::const_iterator iter(map.find(key));
    return iter != map.end() ? iter->second : typename T::mapped_type();
}

Я собираюсь эту реализацию, помещая ее в библиотеку поддержки, чтобы нам не нужно было ее повторно реализовывать везде :) Мне бы очень понравилось, если бы STL больше ориентировался на старое доброе удобство из коробки ... Спасибо :)

Magnus Hoff 30.09.2008 15:53

Взгляните и на решение janm! его функция сравнения не вернет истину для ключа, которого нет на карте, в сочетании со сравнением по умолчанию!

xtofl 30.09.2008 15:55

Кстати, единственная причина, по которой функция возвращает V, а не V const &, заключается в том, что при возврате значения по умолчанию V () мы не хотим возвращать висящую ссылку. Если вы готовы гарантировать, что элемент существует, просто выполните «return iter-> second;» (неопределенное поведение, если элемент не существует).

Chris Jester-Young 30.09.2008 15:57

Я думаю, что функция get может быть применима вне простого тестирования на равенство. Я признаю слабость значения по умолчанию.

Magnus Hoff 30.09.2008 16:02

Кажется, лучше работает, когда я добавляю еще один параметр шаблона, K2 как таковой: template <typename K1, typename K2, typename V> V get(std::map<K1, V> const& map, K2 const& key) Таким образом, я могу использовать строковый литерал для ключевого параметра и неявно преобразовать его в std::string.

Magnus Hoff 30.09.2008 17:34

Это можно выразить так, чтобы он работал с hash_map и другими контейнерами (без K1 / K2): template <typename CONT> typename CONT :: mapped_type get (const CONT & m, const typename CONT :: key_type & k) {CONT :: const_iterator я (m.find (k)); return i == m.end ()? CONT :: mapped_type (): i-> второй; }

janm 30.09.2008 18:46

Просто заметил, что Мэтт Прайс сделал то же наблюдение в одном из ответов. Конечно, я согласен с его наблюдениями!

janm 30.09.2008 18:50

janm: Согласен! Я думаю, что вместо этого следует принять ваш ответ или ответ Мэтта, однако пока что я отредактирую свою запись, чтобы учесть это. :-)

Chris Jester-Young 01.10.2008 01:03

Я протестировал свою реализацию как с std :: map, так и с std :: tr1 :: unordered_map, и они оба работают. Конечно, я был суперпедантичным и ставил "typename" на все, что в нем требовалось. :-П

Chris Jester-Young 01.10.2008 01:13

Отбрасывание const неверно, потому что operator [] на карте <> создаст запись, если она не присутствует с построенной по умолчанию строкой. Если карта действительно находится в неизменяемом хранилище, она завершится ошибкой. Это должно быть так, потому что operator [] возвращает неконстантную ссылку, разрешающую присваивание. (например, m [1] = 2)

Быстрая бесплатная функция для сравнения:

template<typename CONT>
bool check_equal(const CONT& m, const typename CONT::key_type& k,
                    const typename CONT::mapped_type& v)
{
    CONT::const_iterator i(m.find(k));
    if (i == m.end()) return false;
    return i->second == v;
}

Я подумаю о синтаксическом сахаре и обновлю, если что-нибудь придумаю.

...

Непосредственный синтаксический сахар включает бесплатную функцию, которая выполняет map <> :: find () и возвращает специальный класс, который обертывает map <> :: const_iterator, а затем имеет перегруженные operator == () и operator! = (), Чтобы разрешить сравнение с отображаемым типом. Итак, вы можете сделать что-то вроде:

if (nonmutating_get(m, "key") == "value") { ... }

Я не уверен, что это намного лучше, чем:

if (check_equal(m, "key", "value")) { ... }

И это, конечно, намного сложнее, и то, что происходит, гораздо менее очевидно.

Цель объекта-оболочки итератора - перестать иметь построенные по умолчанию объекты данных. Если вам все равно, просто используйте ответ «получить».

В ответ на комментарий о том, что get предпочтительнее сравнения в надежде найти какое-то применение в будущем, у меня есть следующие комментарии:

  • Скажите, что вы имеете в виду: вызов функции с именем «check_equal» дает понять, что вы выполняете сравнение на равенство без создания объекта.

  • Я рекомендую реализовывать функциональность только тогда, когда она вам нужна. Делать что-то до этого - часто ошибка.

  • В зависимости от ситуации конструктор по умолчанию может иметь побочные эффекты. Если сравниваешь, зачем делать лишнее?

  • Аргумент SQL: NULL не эквивалентен пустой строке. Действительно ли отсутствие ключа в вашем контейнере то же самое, что и ключ в вашем контейнере со сконструированным значением по умолчанию?

Сказав все это, созданный по умолчанию объект эквивалентен использованию map <> :: operator [] в неконстантном контейнере. И, возможно, у вас есть текущие требования к функции get, которая возвращает сконструированный объект по умолчанию; Я знаю, что раньше у меня было это требование.

Это хорошая идея, но я предпочитаю get-реализацию (даже с ее слабостью) в надежде, что эту функцию можно будет использовать вне тестирования на равенство.

Magnus Hoff 30.09.2008 16:00

janm: Действительно, мне больше всего нравится ваше решение. Я также согласен с аргументом NULL. Вы можете, например, сказать check_equal ("foo", "") и вернуть true только в том случае, если map ["foo"] существует и пуста. get (map, "foo") == "" говорит о другом.

Chris Jester-Young 30.09.2008 16:39

Вы дадите в целом хороший совет по кодированию C++, спасибо. Я действительно больше ищу, как читать из константной карты, чем как сравнивать значение с одним в константной карте. Я уверен, что для базы кода, над которой я работаю, функция get будет хорошим дополнением. Еще раз спасибо :)

Magnus Hoff 30.09.2008 16:42

Интересно отметить, что есть два способа обнаружения типа шаблона в принятой реализации get (тот, который получает значение или возвращает созданный по умолчанию объект). Во-первых, вы можете делать то, что было принято, и иметь:

template <typename K, typename V>
V get1(const std::map<K, V>& theMap, const K const key)
{
    std::map<K, V>::const_iterator iter(theMap.find(key));
    return iter != theMap.end() ? iter->second : V();
}

или вы можете использовать тип карты и получить от него типы:

template<typename T>
typename T::mapped_type
get2(const T& theMap, const typename T::key_type& key)
{
    typename T::const_iterator itr = theMap.find(key);
    return itr != theMap.end() ? itr->second : typename T::mapped_type();
}

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

std::map<std::string, int> data;
get1(data, "hey"); // doesn't compile because the key type is ambiguous
get2(data, "hey"); // just fine, a const char* can be converted to a string

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

Chris Jester-Young 01.10.2008 01:16

std::map<std::string, std::string>::const_iterator it( m.find("a") );
BOOST_CHECK_EQUAL( 
                     ( it == m.end() ? std::string("") : it->second ), 
                     "b" 
                 );

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

Продолжение идеи xtofl о специализации контейнера карты. Будет ли хорошо работать следующее?

template <typename K,typename V>  
struct Dictionary:public std::map<K,V>  
{  
  const V& operator[] (const K& key) const  
  {  
    std::map<K,V>::const_iterator iter(this->find(key));  
    BOOST_VERIFY(iter!=this->end());  
    return iter->second;  
  }  
};  

Другие вопросы по теме