Чи є спосіб задати значення за замовчуванням std::map«s operator[]повертається , коли ключ не існує?
Відповіді:
Ні, немає. Найпростішим рішенням є написання власної функції безкоштовного шаблону для цього. Щось на зразок:
#include <string>
#include <map>
using namespace std;
template <typename K, typename V>
V GetWithDef(const std::map <K,V> & m, const K & key, const V & defval ) {
typename std::map<K,V>::const_iterator it = m.find( key );
if ( it == m.end() ) {
return defval;
}
else {
return it->second;
}
}
int main() {
map <string,int> x;
...
int i = GetWithDef( x, string("foo"), 42 );
}
C ++ 11 Оновлення
Призначення: Облік загальних асоціативних контейнерів, а також додаткових параметрів порівняння та розподілу.
template <template<class,class,class...> class C, typename K, typename V, typename... Args>
V GetWithDef(const C<K,V,Args...>& m, K const& key, const V & defval)
{
typename C<K,V,Args...>::const_iterator it = m.find( key );
if (it == m.end())
return defval;
return it->second;
}
operator[]із значенням за замовчуванням, значення за замовчуванням слід вставити на карту всередині if ( it == m.end() )блоку
Хоча це точно не відповідає на питання, я вирішив проблему з таким кодом:
struct IntDefaultedToMinusOne
{
int i = -1;
};
std::map<std::string, IntDefaultedToMinusOne > mymap;
Стандарт C ++ (23.3.1.2) вказує, що щойно вставлене значення побудовано за замовчуванням, тому mapсам не надає способу це зробити. Ваш вибір:
operator[]для вставки цього за замовчуванням.Більш загальна версія, підтримка C ++ 98/03 та більше контейнерів
Працює із загальними асоціативними контейнерами, єдиним параметром шаблону є сам тип контейнера.
Підтримувані контейнери: std::map, std::multimap, std::unordered_map, std::unordered_multimap, wxHashMap, QMap, QMultiMap, QHash, QMultiHash, і т.д.
template<typename MAP>
const typename MAP::mapped_type& get_with_default(const MAP& m,
const typename MAP::key_type& key,
const typename MAP::mapped_type& defval)
{
typename MAP::const_iterator it = m.find(key);
if (it == m.end())
return defval;
return it->second;
}
Використання:
std::map<int, std::string> t;
t[1] = "one";
string s = get_with_default(t, 2, "unknown");
Ось така реалізація за допомогою класу - оболонки, який більше схожий на метод get()з dictтипу в Python: https://github.com/hltj/wxMEdit/blob/master/src/xm/xm_utils.hpp
template<typename MAP>
struct map_wrapper
{
typedef typename MAP::key_type K;
typedef typename MAP::mapped_type V;
typedef typename MAP::const_iterator CIT;
map_wrapper(const MAP& m) :m_map(m) {}
const V& get(const K& key, const V& default_val) const
{
CIT it = m_map.find(key);
if (it == m_map.end())
return default_val;
return it->second;
}
private:
const MAP& m_map;
};
template<typename MAP>
map_wrapper<MAP> wrap_map(const MAP& m)
{
return map_wrapper<MAP>(m);
}
Використання:
std::map<int, std::string> t;
t[1] = "one";
string s = wrap_map(t).get(2, "unknown");
C ++ 17 забезпечує, try_emplaceщо саме це робить. Він бере ключ і список аргументів для конструктора значень і повертає пару: an iteratorі a bool.: Http://en.cppreference.com/w/cpp/container/map/try_emplace
Неможливо вказати значення за замовчуванням - це завжди значення, побудоване за замовчуванням (конструктор нульових параметрів).
Насправді, operator[]мабуть, робить більше, ніж ви очікуєте, ніби значення не існує для даного ключа на карті, воно вставить нове зі значенням із конструктора за замовчуванням.
findякі повертають кінцевий ітератор, якщо для даного ключа не існує елемента.
findв цьому випадку складність часу ?
template<typename T, T X>
struct Default {
Default () : val(T(X)) {}
Default (T const & val) : val(val) {}
operator T & () { return val; }
operator T const & () const { return val; }
T val;
};
<...>
std::map<KeyType, Default<ValueType, DefaultValue> > mapping;
Значення ініціалізується за допомогою конструктора за замовчуванням, як кажуть інші відповіді. Однак корисно додати, що у випадку простих типів (цілісні типи, такі як int, float, покажчик або типи POD (планування старих даних)), значення нульово ініціалізуються (або обнуляються ініціалізацією значень (що ефективно те саме), залежно від того, яка версія C ++ використовується).
У будь-якому випадку, суть полягає в тому, що карти з простими типами автоматично ініціалізують нові елементи. Тож у деяких випадках не потрібно турбуватися про чітке вказівку початкового значення за замовчуванням.
std::map<int, char*> map;
typedef char *P;
char *p = map[123],
*p1 = P(); // map uses the same construct inside, causes zero-initialization
assert(!p && !p1); // both will be 0
Див. Чи мають дужки після назви типу різницю з новими? для детальної інформації.
Одним з обхідних шляхів є використання map::at()замість []. Якщо ключ не існує, atвидається виняток. Ще приємніше, це також працює для векторів, і, отже, підходить для загального програмування, де ви можете поміняти карту на вектор.
Використання спеціального значення для незареєстрованого ключа може бути небезпечним, оскільки це спеціальне значення (наприклад, -1) може бути оброблено далі в коді. За винятком, виявити помилки простіше.
Можливо, ви можете надати власний розподільник, який розподіляє з типовим значенням, яке ви хочете.
template < class Key, class T, class Compare = less<Key>,
class Allocator = allocator<pair<const Key,T> > > class map;
operator[]повертає об'єкт, створений за допомогою виклику T(), незалежно від того, що робить розподільник.
constructметод розподілювачів ? Це можна було б змінити, я думаю. Однак я підозрюю, що constructфункція, яка виконує щось інше, ніж new(p) T(t);не добре сформована. РЕДАГУВАТИ: Оглянувшись назад, це було безглуздо, інакше всі значення були б однаковими: P Де моя кава ...
operator[]повертається (*((insert(make_pair(x, T()))).first)).second. Отже, якщо я чогось не пропустив, ця відповідь неправильна.
insertз a T(), але всередині вставки - це коли він буде використовувати розподільник, щоб отримати пам'ять для нового, Tпотім виклику constructцієї пам'яті з параметром, який він отримав, тобто T(). Отже, дійсно можна змінити поведінку, operator[]щоб повернути щось інше, але розподільник не може диференціювати, чому його викликають. Отже, навіть якщо ми змусимо constructігнорувати його параметр і використовувати наше спеціальне значення, це означало б, що кожен побудований елемент мав це значення, що погано.
Розширюючись до відповіді https://stackoverflow.com/a/2333816/272642 , ця функція шаблону використовує std::map' key_typeі mapped_typetypedefs для виведення типу keyта def. Це не працює з контейнерами без цих typedefs.
template <typename C>
typename C::mapped_type getWithDefault(const C& m, const typename C::key_type& key, const typename C::mapped_type& def) {
typename C::const_iterator it = m.find(key);
if (it == m.end())
return def;
return it->second;
}
Це дозволяє використовувати
std::map<std::string, int*> m;
int* v = getWithDefault(m, "a", NULL);
без необхідності наводити аргументи типу std::string("a"), (int*) NULL.
Використовуйте std::map::insert().
Розуміючи, що я досить запізнився на цю вечірку, але якщо вас цікавить поведінка operator[]користувацьких налаштувань за замовчуванням (тобто, знайдіть елемент із заданим ключем, якщо його немає, вставте елемент на карту за допомогою вибрано значення по замовчуванням , і повертає посилання на будь-який з знову вставленого значення або існуюче значення), Існує вже функція доступна вам попередньо C ++ 17: std::map::insert(). insertнасправді не буде вставляти, якщо ключ уже існує, а замість цього повертати ітератор до існуючого значення.
Скажімо, ви хотіли карту string-to-int і вставляєте значення за замовчуванням 42, якщо ключа ще не було:
std::map<std::string, int> answers;
int count_answers( const std::string &question)
{
auto &value = answers.insert( {question, 42}).first->second;
return value++;
}
int main() {
std::cout << count_answers( "Life, the universe and everything") << '\n';
std::cout << count_answers( "Life, the universe and everything") << '\n';
std::cout << count_answers( "Life, the universe and everything") << '\n';
return 0;
}
який повинен виводити 42, 43 і 44.
Якщо вартість побудови значення карти висока (якщо копіювання / переміщення ключа або тип значення є дорогими), це призводить до значного штрафу за продуктивність, який, на мою думку, можна обійти за допомогою C ++ 17 try_emplace.