Is it defined and valid behavior that insert through pass-the-end iterator returned by find when the key is not found:
auto it = m.find(key);
if (it == m.end()) {
m.insert(it, make_pair(key, value));
}
because this will save an additional lookup compared using:
m[key] = value;
While it's safe to pass an end iterator as a hint to to unordered_map::insert, it doesn't actually accomplish anything.
Of the three major standard library implementations, only libstdc++ does anything with that hint, and even then it will only end up using it if it points to a valid entry.
If you want to avoid doing two lookups (one to determine if the element is present and another to insert it), you should just try to insert it. insert returns both a bool denoting whether a new element was inserted and an iterator to either the newly-inserted element or the existing element that prevented insertion. That means the most efficient way to insert an element if it doesn't exist and get an iterator to the element is to do something like this:
decltype(m)::iterator it;
bool inserted;
std::tie(it, inserted) = m.insert(std::make_pair(key, value));
if (inserted) {
// ...
}
If your mapped_type is expensive to construct, you can avoid building it with try_emplace (only available with C++17 or later):
auto [it, inserted] = m.try_emplace(key, args, to, value, constructor);
if (inserted) {
// ...
}
Pre C++17, you can just let operator[] default-construct the element and compare the container size to determine if a new element was added:
size_t size_before = m.size();
ValueType& element = m[key];
size_t size_after = m.size();
if (size_before != size_after) {
element = ValueType{args, to, value, constructor};
// ...
}
Obviously this has the drawback of default-constructing the element and only working with assignable types.
Related
I'm trying to write a substitute for std::map::insert_or_assign that takes the hint parameter, for build environments that don't support C++17.
I'd like for this substitute to be just as efficient, and not require that the mapped type be DefaultConstructible. The latter requirement rules out map[key] = value.
I've come up with this:
template <class M, class K, class T>
typename M::iterator insert_or_assign(M& map, typename M::const_iterator hint,
K&& key, T&& value)
{
using std::forward;
auto old_size = map.size();
auto iter = map.emplace_hint(hint, forward<K>(key), forward<T>(value));
// If the map didn't grow, the key already already existed and we can directly
// assign its associated value.
if (map.size() == old_size)
iter->second = std::forward<T>(value);
return iter;
}
However, I don't know if I can trust std::map not to move-assign the value twice in the case where the key already existed. Is this safe? If not, is there a safe way to efficiently implement a substitute for std::map::insert_or_assign taking a hint parameter?
As per NathanOliver's comment, where he cited the cppreference documentation for std::map::emplace:
The element may be constructed even if there already is an element
with the key in the container, in which case the newly constructed
element will be destroyed immediately.
If we assume the same applies for std::map::emplace_hint, then the value could moved away prematurely in the solution I proposed in my question.
I've come up with this other solution (NOT TESTED), which only forwards the value once. I admit it's not pretty. :-)
// Take 'hint' as a mutating iterator to avoid an O(N) conversion.
template <class M, class K, class T>
typename M::iterator insert_or_assign(M& map, typename M::iterator hint,
K&& key, T&& value)
{
using std::forward;
#ifdef __cpp_lib_map_try_emplace
return map.insert_or_assign(hint, forward<K>(key), forward<T>(value);
#else
// Check if the given key goes between `hint` and the entry just before
// hint. If not, check if the given key matches the entry just before hint.
if (hint != map.begin())
{
auto previous = hint;
--previous; // O(1)
auto comp = map.key_comp();
if (comp(previous->first, key)) // key follows previous
{
if (comp(key, hint->first)) // key precedes hint
{
// Should be O(1)
return map.emplace_hint(hint, forward<K>(key),
forward<T>(value));
}
}
else if (!comp(key, previous->first)) // key equals previous
{
previous->second = forward<T>(value); // O(1)
return previous;
}
}
// If this is reached, then the hint has failed.
// Check if key already exists. If so, assign its associated value.
// If not, emplace the new key-value pair.
auto iter = map.find(key); // O(log(N))
if (iter != map.end())
iter->second = forward<T>(value);
else
iter = map.emplace(forward<K>(key), forward<T>(value)); // O(log(N))
return iter;
#endif
}
I hope somebody else will come up with a nicer solution!
Note that I check for the __cpp_lib_map_try_emplace feature test macro to test if std::map::insert_or_assign is supported before resorting to this ugly mess.
EDIT: Removed the the slow iterator arithmetic silliness in attempting to check if the key already exists at hint.
EDIT 2: hint is now taken as a mutating iterator to avoid an expensive O(N) conversion if it was otherwise passed as a const_iterator. This allows me to manually check the hint and perform an O(1) insertion or assignment if the hint succeeds.
void main()
{
std::map<int,int>keyValueMap;
keyValueMap.insert(0,1);
keyValueMap.insert(0,2);
int index = keyValueMap.begin()->second;
}
the value of index at thispoint is 1 and not 2 can someone explain why ?
From this std::map::insert reference:
Inserts element(s) into the container, if the container doesn't already contain an element with an equivalent key.
[Emphasis by me]
You can't use insert to overwrite existing data in the map.
All of the 2-param overloads of std::map::insert() require iterators, not key/value pairs.
Most standard container insert() methods take the container's value_type as input. The value_type of a std::map is a std::pair. So, when inserting a key/value pair into a std::map, you have to pass them in to insert() as a std::pair, eg:
void main()
{
std::map<int,int> keyValueMap;
keyValueMap.insert(std::make_pair(0, 1));
keyValueMap.insert(std::make_pair(0, 2));
int index = keyValueMap.begin()->second;
}
Now, that being said, the second insert() will still not overwrite the value from the first insert(), even though they both have the same key 0. The second insert will simply be ignored:
Inserts element(s) into the container, if the container doesn't already contain an element with an equivalent key.
...
Return value
1-3) Returns a pair consisting of an iterator to the inserted element (or to the element that prevented the insertion) and a bool denoting whether the insertion took place.
To overwrite the first value with the second value, you can either:
use the iterator that insert() returns:
void main()
{
std::map<int,int> keyValueMap;
keyValueMap.insert(std::make_pair(0, 1));
auto ret = keyValueMap.insert(std::make_pair(0, 2));
if (!ret.second) ret.first->second = 2;
int index = keyValueMap.begin()->second;
}
use the map's operator[] instead:
void main()
{
std::map<int,int> keyValueMap;
keyValueMap[0] = 1;
keyValueMap[0] = 2;
int index = keyValueMap.begin()->second;
}
I want to iterate over all items in a std::multimap (all values of all keys), and delete all entries that satisfy some condition:
#include <map>
typedef int KEY_TYPE;
typedef int VAL_TYPE;
bool shouldRemove(const KEY_TYPE&, const VAL_TYPE&);
void removeFromMap(std::multimap<KEY_TYPE,VAL_TYPE>& map){
for (auto it = map.begin(); it != map.end(); it++){
if (shouldRemove(it->first,it->second))
map.erase(it);
}
}
The iteration works unless the first item gets deleted, and the following error is thrown then:
map/set iterator not incrementable
How can the removeFromMap function be rewritten in order to work properly? The code should work for all kinds of key- and value types of the map.
I am using C++ 11 and Visual Studio 2013.
You need to increment your iterator before you do the erase. When you do map.erase(it); the iterator it becomes invalid. However, other iterators in the map will still be valid. Therefore you can fix this by doing a post-increment on the iterator...
auto it = map.begin();
const auto end = map.end();
while (it != end)
{
if (shouldRemove(it->first,it->second))
{
map.erase(it++);
// ^^ Note the increment here.
}
else
{
++it;
}
}
The post-increment applied to it inside the map.erase() parameters will ensure that it remains valid after the item is erased by incrementing the iterator to point to the next item in the map just before erasing.
map.erase(it++);
... is functionally equivalent to...
auto toEraseIterator = it; // Remember the iterator to the item we want to erase.
++it; // Move to the next item in the map.
map.erase(toEraseIterator); // Erase the item.
As #imbtfab points out in the comments, you can also use it = map.erase(it) to do the same thing in C++11 without the need for post-incrementing.
Note also that the for loop has now been changed to a while loop since we're controlling the iterator manually.
Additionally, if you're looking to make your removeFromMap function as generic as possible, you should consider using template parameters and pass your iterators in directly, rather than passing in a reference to the multi-map. This will allow you to use any map-style container type, rather than forcing a multimap to be pased in.
e.g.
template <typename Iterator>
void removeFromMap(Iterator it, const Iterator &end){
...
}
This is how the standard C++ <algorithm> functions do it also (e.g. std::sort(...)).
I've got a bit of a conundrum here. I am writing a mesh (grid) generator. In doing so, I need to obtain a list of lower dimensional elements from higher dimensional elements (i.e. a "triangle" element is composed of 3 "line" elements). I need the list of unique line elements and I need the parent element to store a pointer to each of its unique child elements.
To this end, I create a std::set of smart pointers to the child elements and try to insert each child element to the list. Every time I try to insert an element I ask for a pair containing an iterator to the pre-existing element and a boolean specifying whether or not the element has been inserted.
The problem is that std::set (and map) return constant iterators to the pre-existing element. However, I need to give the parent element of the element that I failed to insert a non-constant pointer to the pre-existing element. So I use const_pointer_cast<> to cast the constant iterator to a non-constant one.
Is there a better way of going about this? Doing a const_pointer_cast() is probably not ideal. Is there something that I should be using for this application instead of std::set?
Edit: here's the function:
template<class T1, class T2>
int getUniqueSubelements(std::set<std::shared_ptr<T1>, elGreater>& elements,
std::set<std::shared_ptr<T2>, elGreater>& childels)
{
typedef std::shared_ptr<T2> child_ptr;
std::pair<typename std::set<child_ptr, elGreater>::iterator, bool> ret;
for (auto parit = elements.begin(); parit != elements.end(); ++parit)
{
(*parit)->generateChildren();
const std::vector<child_ptr>& children = (*parit)->getChildren();
for (int i = 0; i < children.size(); i++)
{
ret = childels.insert(children[i]);
if (ret.second == false)
{
child_ptr temp = std::const_pointer_cast<T2>(*ret.first);
bool orient = elOrientation(children[i], temp);
children[i]->swapInParent(temp, orient);
}
}
}
return 1;
}
No cast at all is needed here.
It's true that std::set<K, Comp>::iterator is the same as std::set<K, Comp>::const_iterator, and that iterator::operator* returns a const K&.
But in your case, this means that the type of *ret.first is const std::shared_ptr<T2>&. This is the analogue of (T2* const), not (const T2*): you can't modify the smart pointer, but you can modify the actual stored object.
So assuming elOrientation is something like
template <typename T>
bool elOrientation(std::shared_ptr<T>, std::shared_ptr<T>);
you can just call elOrientation(children[i], *ret.first).
I'm using the STL map data structure, and at the moment my code first invokes find(): if the key was not previously in the map, it calls insert() it, otherwise it does nothing.
map<Foo*, string>::iterator it;
it = my_map.find(foo_obj); // 1st lookup
if(it == my_map.end()){
my_map[foo_obj] = "some value"; // 2nd lookup
}else{
// ok do nothing.
}
I was wondering if there is a better way than this, because as far as I can tell, in this case when I want to insert a key that is not present yet, I perform 2 lookups in the map data structures: one for find(), one in the insert() (which corresponds to the operator[] ).
Thanks in advance for any suggestion.
Normally if you do a find and maybe an insert, then you want to keep (and retrieve) the old value if it already existed. If you just want to overwrite any old value, map[foo_obj]="some value" will do that.
Here's how you get the old value, or insert a new one if it didn't exist, with one map lookup:
typedef std::map<Foo*,std::string> M;
typedef M::iterator I;
std::pair<I,bool> const& r=my_map.insert(M::value_type(foo_obj,"some value"));
if (r.second) {
// value was inserted; now my_map[foo_obj]="some value"
} else {
// value wasn't inserted because my_map[foo_obj] already existed.
// note: the old value is available through r.first->second
// and may not be "some value"
}
// in any case, r.first->second holds the current value of my_map[foo_obj]
This is a common enough idiom that you may want to use a helper function:
template <class M,class Key>
typename M::mapped_type &
get_else_update(M &m,Key const& k,typename M::mapped_type const& v) {
return m.insert(typename M::value_type(k,v)).first->second;
}
get_else_update(my_map,foo_obj,"some value");
If you have an expensive computation for v you want to skip if it already exists (e.g. memoization), you can generalize that too:
template <class M,class Key,class F>
typename M::mapped_type &
get_else_compute(M &m,Key const& k,F f) {
typedef typename M::mapped_type V;
std::pair<typename M::iterator,bool> r=m.insert(typename M::value_type(k,V()));
V &v=r.first->second;
if (r.second)
f(v);
return v;
}
where e.g.
struct F {
void operator()(std::string &val) const
{ val=std::string("some value")+" that is expensive to compute"; }
};
get_else_compute(my_map,foo_obj,F());
If the mapped type isn't default constructible, then make F provide a default value, or add another argument to get_else_compute.
There are two main approaches. The first is to use the insert function that takes a value type and which returns an iterator and a bool which indicate if an insertion took place and returns an iterator to either the existing element with the same key or the newly inserted element.
map<Foo*, string>::iterator it;
it = my_map.find(foo_obj); // 1st lookup
my_map.insert( map<Foo*, string>::value_type(foo_obj, "some_value") );
The advantage of this is that it is simple. The major disadvantage is that you always construct a new value for the second parameter whether or not an insertion is required. In the case of a string this probably doesn't matter. If your value is expensive to construct this may be more wasteful than necessary.
A way round this is to use the 'hint' version of insert.
std::pair< map<foo*, string>::iterator, map<foo*, string>::iterator >
range = my_map.equal_range(foo_obj);
if (range.first == range.second)
{
if (range.first != my_map.begin())
--range.first;
my_map.insert(range.first, map<Foo*, string>::value_type(foo_obj, "some_value") );
}
The insertiong is guaranteed to be in amortized constant time only if the element is inserted immediately after the supplied iterator, hence the --, if possible.
Edit
If this need to -- seems odd, then it is. There is an open defect (233) in the standard that hightlights this issue although the description of the issue as it applies to map is clearer in the duplicate issue 246.
In your example, you want to insert when it's not found. If default construction and setting the value after that is not expensive, I'd suggest simpler version with 1 lookup:
string& r = my_map[foo_obj]; // only lookup & insert if not existed
if (r == "") r = "some value"; // if default (obj wasn't in map), set value
// else existed already, do nothing
If your example tells what you actually want, consider adding that value as str Foo::s instead, you already have the object, so no lookups would be needed, just check if it has default value for that member. And keep the objs in the std::set. Even extending class FooWithValue2 may be cheaper than using map.
But If joining data through the map like this is really needed or if you want to update only if it existed, then Jonathan has the answer.