Идиоматический С++ для чтения с карты const
Для 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[]
. Что мне делать? Есть ли стандартный способ сделать это (красиво)?
Изменить: Просто чтобы указать ответ, который никто из вас не хочет дать: Нет, нет удобного, красивого, стандартного способа сделать это на С++. Мне придется реализовать вспомогательную функцию.
Ответы
Ответ 1
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();
}
Ответ 2
Отбрасывание const неверно, потому что оператор [] на карте < > создаст запись, если она отсутствует с построенной по умолчанию строкой. Если карта на самом деле находится в неизменяемом хранилище, она будет терпеть неудачу. Это должно быть так, потому что оператор [] возвращает неконстантную ссылку, чтобы разрешить назначение. (например, 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;
}
Я подумаю о синтаксическом сахаре и обновлении, если что-то придумаю.
...
Непосредственный синтаксический сахар включает свободную функцию, которая делает карту < > :: find() и возвращает специальный класс, который обертывает map < > :: const_iterator, а затем перегружает оператор ==() и operator!= (), чтобы обеспечить сопоставление с отображаемым типом. Поэтому вы можете сделать что-то вроде:
if (nonmutating_get(m, "key") == "value") { ... }
Я не уверен, что это намного лучше, чем:
if (check_equal(m, "key", "value")) { ... }
И это, безусловно, намного сложнее, а то, что происходит, гораздо менее очевидно.
Целью обертывания объекта итератором является прекращение создания построенных по умолчанию объектов данных. Если вам все равно, просто используйте ответ "получить".
В ответ на комментарий о том, чтобы получить преимущество перед сравнением в надежде найти какое-то будущее использование, у меня есть следующие комментарии:
-
Скажите, что вы имеете в виду: вызов функции под названием "check_equal" дает понять, что вы выполняете сравнение равенства без создания объекта.
-
Я рекомендую использовать функциональность только после того, как вам понадобится. Делать что-то до этого часто бывает ошибкой.
-
В зависимости от ситуации конструктор по умолчанию может иметь побочные эффекты. Если вы сравниваете, зачем делать что-то лишнее?
-
Аргумент SQL: NULL не эквивалентен пустой строке. Является ли отсутствие ключа из вашего контейнера тем же самым, что и ключ, присутствующий в вашем контейнере, со значением по умолчанию?
Сказав все это, построенный по умолчанию объект эквивалентен использованию map < > :: operator [] в контейнере, отличном от const. И, возможно, у вас есть текущее требование для функции get, которая возвращает построенный по умолчанию объект; Я знаю, что у меня было это требование в прошлом.
Ответ 3
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
в приведенном выше случае.
Ответ 4
Интересно, что существует два способа обнаружения типа шаблона в реализации реализации, который был принят (тот, который получает значение или возвращает построенный по умолчанию объект). Во-первых, вы можете делать то, что было принято, и иметь:
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
Ответ 5
Действительно, оператор [] является неконстантным на std:: map, так как он автоматически вставляет пару ключ-значение в карту, если ее не было. (Oooh побочные эффекты!)
Правильный способ использует 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
.
Ответ 6
std::map<std::string, std::string>::const_iterator it( m.find("a") );
BOOST_CHECK_EQUAL(
( it == m.end() ? std::string("") : it->second ),
"b"
);
Это не выглядит слишком плохо для меня... Я, вероятно, не буду писать функцию для этого.
Ответ 7
Следующее 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;
}
};