Для 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++ нет удобного, красивого, стандартного способа сделать это. Придется реализовать функцию поддержки.





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 в приведенном выше случае.
Для большинства типов конструкции по умолчанию не должны быть слишком дорогими. Если да - не используйте аргумент по умолчанию.
Это или есть перегруженные версии, одна с вашей версией (но без значения по умолчанию), а другая с моей. :-)
Заметьте, мое предложение, приведенное выше, является излишним для вопроса. :-П
Я думаю, что у вашего решения есть одно преимущество: вы можете вернуться с помощью T const & (полезно, если, например, T дорого копировать). Так что да, я тоже проголосовал за вашу запись (помимо большей общности). :-)
Действительно, 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 больше ориентировался на старое доброе удобство из коробки ... Спасибо :)
Взгляните и на решение janm! его функция сравнения не вернет истину для ключа, которого нет на карте, в сочетании со сравнением по умолчанию!
Кстати, единственная причина, по которой функция возвращает V, а не V const &, заключается в том, что при возврате значения по умолчанию V () мы не хотим возвращать висящую ссылку. Если вы готовы гарантировать, что элемент существует, просто выполните «return iter-> second;» (неопределенное поведение, если элемент не существует).
Я думаю, что функция get может быть применима вне простого тестирования на равенство. Я признаю слабость значения по умолчанию.
Кажется, лучше работает, когда я добавляю еще один параметр шаблона, K2 как таковой: template <typename K1, typename K2, typename V> V get(std::map<K1, V> const& map, K2 const& key) Таким образом, я могу использовать строковый литерал для ключевого параметра и неявно преобразовать его в std::string.
Это можно выразить так, чтобы он работал с 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: Согласен! Я думаю, что вместо этого следует принять ваш ответ или ответ Мэтта, однако пока что я отредактирую свою запись, чтобы учесть это. :-)
Я протестировал свою реализацию как с std :: map, так и с std :: tr1 :: unordered_map, и они оба работают. Конечно, я был суперпедантичным и ставил "typename" на все, что в нем требовалось. :-П
Отбрасывание 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-реализацию (даже с ее слабостью) в надежде, что эту функцию можно будет использовать вне тестирования на равенство.
janm: Действительно, мне больше всего нравится ваше решение. Я также согласен с аргументом NULL. Вы можете, например, сказать check_equal ("foo", "") и вернуть true только в том случае, если map ["foo"] существует и пуста. get (map, "foo") == "" говорит о другом.
Вы дадите в целом хороший совет по кодированию C++, спасибо. Я действительно больше ищу, как читать из константной карты, чем как сравнивать значение с одним в константной карте. Я уверен, что для базы кода, над которой я работаю, функция get будет хорошим дополнением. Еще раз спасибо :)
Интересно отметить, что есть два способа обнаружения типа шаблона в принятой реализации 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 о том, что он не зависит от контейнера. Между прочим, конечный результат очень похож на ваш (я написал свою собственную реализацию, но я думаю, который просто не так много способов ее написать). :-П
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;
}
};
Ой! Это означает создание строки по умолчанию для каждого вызова, когда
default(кстати, ключевое слово) не указан.