Reimundo Heluani
Reimundo Heluani

Reputation: 978

add to std::map with a non-default-constructible mapped value

I have a std::map<Key, T> with a non-default-constructible T. T operloads operator + so that I know how to add objects of T. I am frequently in the situation that I need to add a particular value at at a given Key k. If T were default constructible I would do something like

std::map<Key, T> map;
Key k;
T t;

map[k] += t;

However I am forced to use expressions like

if (map.contains(k)) map.at(k) += t;
else map.emplace(k, t);

Is there a way of avoiding the lookup and behave like [] would do with a default value? Do I need to change the allocator of map so that by default inserts t?

Upvotes: 1

Views: 246

Answers (2)

GManNickG
GManNickG

Reputation: 503963

What you want, in general, is try_emplace. This is in C++17.

This function will emplace if and only if the key is missing, otherwise it is guaranteed to do nothing to the value. This is unlike insert or emplace, which may freely copy or move your value even if the key exists. This is important if your type is like unique_ptr, or to avoid unnecessary copies.

#include <iostream>
#include <map>
#include <string>

struct Foo {
    explicit Foo(int i) : i(i) {}
    Foo(const Foo&) = delete;
    Foo(Foo&&) = default;
    
    Foo& operator+=(const Foo& other) {
        i += other.i;
        return *this;
    }
    
    int i;
};

template <typename K, typename V>
V& emplace_or_add(std::map<K, V> & map, const K& key, V&& value) {
    auto [iter, was_emplaced] = map.try_emplace(key, std::forward<V>(value));
    if (!was_emplaced) {
        iter->second += value;
    }
    
    return iter->second;
}

int main() {
    std::map<char, Foo> map;
    
    map.emplace('a', 5);
    
    emplace_or_add(map, 'a', Foo(3));
    emplace_or_add(map, 'b', Foo(2));
    
    std::cout << "a: " << map.at('a').i << std::endl;
    std::cout << "b: " << map.at('b').i << std::endl;
}

http://coliru.stacked-crooked.com/a/2b739a55ad791507

a: 8
b: 2

Upvotes: 2

Jarod42
Jarod42

Reputation: 217448

To look-up only once, you might do something like:

if (auto [it, inserted] = map.insert(k, t); !inserted) {
    *it += t;
}

Upvotes: 3

Related Questions