I have the following problem. I have a std::unordered_map
that contains an object as the value. Now I want to modify an object that I previously inserted.
class Point
{
public:
Point(float _x, float _y) : x(_x), y(_y) {}
float x;
float y;
};
std::unordered_map<int, Point> points;
// ... add some values to it ...
points[1].x = 20.f; // error?
I get a weird long compile error about point not being able to be default constructed. The way I understand it operator []
returns a reference to the mapped type (aka the value), so why can't I modify it?
If the key isn't in the map, operator []
is required to create one. The expression
points[1]
needs to be able to default-insert a Point
in case of lookup failure (regardless of whether lookup failure ever occurs - this is a compile-time requirement not a run-time check). That requirement cannot be satisfied by Point
because Point
is not default constructible. Hence the compile error. If you want to use unordered_map::operator[]
, you'll need to add a default constructor.
If a default constructed Point
doesn't make sense for your usage - then you simply cannot use operator[]
and will have to use find
throughout (or at()
if you're okay with exceptions):
auto it = points.find(1);
if (it != points.end()) {
it->second.x = 20.f;
}
points.at(1).x = 20.f; // can throw
operator[]
constructs an object of mapped type in-place if no element exists with the given key. In a map with a default allocator , operator[]
requires the mapped type to be default constructible . More generally, the mapped type must be emplace constuctible .
The easy solution is to add a default constructor to your class.
Point() : Point(0.f, 0.f) {}
If this isn't possible, you will have to use other functions to access map elements.
To access an existing mapped object, you can using at
, which will throw a std::out_of_range
exception if no element exists with the given key.
points.at(1).x = 20.f;
Alternatively, you can use find
, which returns an iterator to the element with the given key, or to the element following the last element in the map (see end
) if no such element exists.
auto it = points.find(1);
if (it != points.end())
{
it->second = 20.f;
}
operator[]
cannot be used on a map
or unordered_map
without the data being default-constructible. This is because if the object is not found, it will create it via default-construction.
The easy solution is to make your type default-constructible.
if not:
template<class M, class K, class F>
bool access_element( M& m, K const& k, F&& f ) {
auto it = m.find(k);
if (it == m.end())
return false;
std::forward<F>(f)(it->second);
return true;
}
then:
std::unordered_map<int, Point> points;
points.emplace(1, Point(10.f, 10.f));
access_element(points, 1, [](Point& elem){
elem.x = 20.f;
});
will do what points[1].x = 20.f;
does without risking exception code or having to make Point
default-constructible.
This pattern -- where we pass a function to mutate/access an element to a container -- is stealing a page from Haskell monad design. I would make it return optional<X>
instead of bool
, where X
is the return type of the passed in function, but that is going a bit far.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.