I'd like to find a value in unordered_set, but failed:
typedef std::shared_ptr<int> IntPtr;
std::unordered_set<IntPtr> s;
s.insert(std::make_shared<int>(42));
bool found = s.find(std::make_shared<int>(42)) != s.end();
cout<<std::boolalpha<<found<<endl; // false
Had tried following but still not working.
namespace std {
template <> struct hash<IntPtr> {
size_t operator()(const IntPtr& x) const noexcept {
return std::hash<int>()(*x);
}
};
}
Any idea how to make it works?
You stored a pointer to an integer. When you look up items in the set, you're not comparing the (pointed-to) integer, but the pointer itself.
When you allocate a new pointer to a new integer object for the search, it won't compare equal, because it's a different integer object (even though it stores the same value).
Your options are:
don't store pointers to integers in your set, just store the integers directly.
Then, your key is 42, and searching for 42 will find it, because the integers are compared by value
store pointers and use a custom hash and comparator to compare the pointed-at integers instead of the pointers.
You shouldn't (try to) pollute std namespace with your hash specialization, and it's not sufficient anyway (the hash is used for bucket lookup, but keys are still compared with KeyEqual inside the bucket). Just specify them for your container.
Example code for #2:
#include <cassert>
#include <memory>
#include <unordered_set>
struct Deref {
struct Hash {
template <typename T>
std::size_t operator() (std::shared_ptr<T> const &p) const {
return std::hash<T>()(*p);
}
};
struct Compare {
template <typename T>
size_t operator() (std::shared_ptr<T> const &a,
std::shared_ptr<T> const &b) const {
return *a == *b;
}
};
};
int main() {
std::unordered_set<std::shared_ptr<int>> sp;
auto p = std::make_shared<int>(42);
sp.insert(p);
assert(sp.find(p) != sp.end()); // same pointer works
assert(sp.find(std::make_shared<int>(42)) == sp.end()); // same value doesn't
// with the correct hash & key comparison, both work
std::unordered_set<std::shared_ptr<int>, Deref::Hash, Deref::Compare> spd;
spd.insert(p);
assert(spd.find(p) != spd.end());
assert(spd.find(std::make_shared<int>(42)) != spd.end());
}
According to here:
Note that the comparison operators for shared_ptr simply compare pointer values; the actual objects pointed to are not compared.
So found will be true only if shared_ptr points to same object:
typedef std::shared_ptr<int> IntPtr;
std::unordered_set<IntPtr> s;
IntPtr p = std::make_shared<int>(42);
s.insert(p);
bool found = s.find(p) != s.end();
cout<<std::boolalpha<<found<<endl; // true
Related
Suppose I have a class called Entry:
template <typename K, typename V>
class Entry {
public:
Entry(K const &key, V const &val, size_t const hash_val) :
key(key), val(val), hash_val(hash_val), empty(false){
}
K getKey() const {
return key;
}
V getValue() const {
return val;
}
size_t getHash() const {
return hash_val;
}
bool isEmpty() const{
return empty;
}
private:
// key-value pair
K key;
V val;
// Store hash for reallocation
size_t hash_val;
// Store empty state
bool empty;
};
Then I create an array of objects
Entry<K, V>** entries = new Entry<K, V> *[100];
If I call entries[0]->isEmpty(), I get a segmentation fault. This makes sense to me, since I haven't actually instantiated a new object in memory. However, I want to be able to check whether a slot in the array actually points to a valid object. Currently, I've been setting each pointer to nullptr so I can check for equality later, but I was wondering if there was a better way.
You want optional. It's always either a valid object, or in an "empty" state.
#include <cstdio>
#include <optional>
#include <vector>
struct Foo {
int bar;
};
int main() {
std::vector<std::optional<Foo>> vfoo{
Foo{1}, std::nullopt, Foo{2}, Foo{3}, std::nullopt,
};
for (auto const& foo : vfoo) {
if (!foo)
std::puts("Not Initialized");
else
std::printf("Foo{%d}\n", foo->bar);
}
}
There is no way to check if a pointer has been initialized, because reading from an unitialized pointer is undefined behavior:
int* p;
if (p == something) ... // undefined behavior
You can initialize the pointer with nullptr and check for that:
int* p = nullptr;
if (p == nullptr) ...
However, then you are initializing the pointer.
For a dynamic array of Entry<K, V>* you can use a std::vector<Entry<K,V>*>. A container that can be used analogous to an array with empty slots is a std::unordered_map:
std::unordered_map<size_t,Entry<K,V>*> map;
Entry<K,V>* ptr = get_pointer_from_somewhere();
size_t index = 42;
if ( map.find(index) == map.end() ) {
// pointer was not initialized (actually pointer is not yet in the map)
map.insert( { index, ptr }); // now it is
}
Actually insert alone will tell you already if the element with key index was present in the map before.
I have a class that stores a std::vector of stuff. In my program, I create a std::unordered_set of std::shared_ptr to objects of this class (see code below). I defined custom functions to compute hashes and equality so that the unordered_set "works" with the objects instead of the pointers. This means: Two different pointers to different objects that have the same content should be treated as equal, let's call it "equivalent".
So far everything worked as expected but now I stumbled across a strange behaviour: I add a pointer to an object to the unordered_set and create a different pointer to a different object with the same content. As said I would expect that my_set.find(different_object) would return a valid iterator to the equivalent pointer stored in the set. But it doesn't.
Here is a minimal working code example.
#include <boost/functional/hash.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <unordered_set>
#include <vector>
class Foo {
public:
Foo() {}
bool operator==(Foo const & rhs) const {
return bar == rhs.bar;
}
std::vector<int> bar;
};
struct FooHash {
size_t operator()(std::shared_ptr<Foo> const & foo) const {
size_t seed = 0;
for (size_t i = 0; i < foo->bar.size(); ++i) {
boost::hash_combine(seed, foo->bar[i]);
}
return seed;
}
};
struct FooEq {
bool operator()(std::shared_ptr<Foo> const & rhs,
std::shared_ptr<Foo> const & lhs) const {
return *lhs == *rhs;
}
};
int main() {
std::unordered_set<std::shared_ptr<Foo>, FooHash, FooEq> fooSet;
auto empl = fooSet.emplace(std::make_shared<Foo>());
(*(empl.first))->bar.emplace_back(0);
auto baz = std::make_shared<Foo>();
baz->bar.emplace_back(0);
auto eqFun = fooSet.key_eq();
auto hashFun = fooSet.hash_function();
if (**fooSet.begin() == *baz) {
std::cout << "Objects equal" << std::endl;
}
if (eqFun(*fooSet.begin(), baz)) {
std::cout << "Keys equal" << std::endl;
}
if (hashFun(*fooSet.begin()) == hashFun(baz)) {
std::cout << "Hashes equal" << std::endl;
}
if (fooSet.find(baz) != fooSet.end()) {
std::cout << "Baz in fooSet" << std::endl;
} else {
std::cout << "Baz not in fooSet" << std::endl;
}
return 0;
}
Output
Objects equal
Keys equal
Hashes equal
And here is the problem:
Baz not in fooSet
What am I missing here? Why does the set not find the equivalent object?
Possibly of interest: I played around with this and found that if my class stores a plain int instead of a std::vector, it works. If I stick to the std::vector but change my constructor to
Foo(int i) : bar{i} {}
and initialize my objects with
std::make_shared<Foo>(0);
it also works. If I remove the whole pointer stuff, It breaks as std::unordered_set::find returns constant iterators and thus modification of objects in the set cannot be done (this way). However, none of these changes is applicable in my real program, anyway.
I compile with g++ version 7.3.0 using -std=c++17
You can't modify an element of a set (and expect the set to work). Because you have provided FooHash and FooEq which inspect the referent's value, that makes the referent part of the value from the point of view of the set!
If we change the initialisation of fooSet to set up the element before inserting it, we get the result you want/expect:
std::unordered_set<std::shared_ptr<Foo>, FooHash, FooEq> fooSet;
auto e = std::make_shared<Foo>();
e->bar.emplace_back(0); // modification is _before_
fooSet.insert(e); // insertion
Looking up the object in the set depends on the hash value not changing. If we really need to modify a member after it has been added, we need to remove it, make the changes, then add the modified object - see Yakk's answer.
To avoid running into issues like this, it may be safer to use std::shared_ptr<const Foo> as elements, which will prevent modification of the pointed-at Foo through the set (although you're still responsible for the use of any non-const pointers you may also have).
Any operation such that the hash or == result of an element in an unordered_set violates the rules of unordered_set is bad; the result is undefined behavior.
You changed the result of a hash of an element in an unordered_set, because your elements are shared pointers, but their hash and == is based off of the value pointed to. And your code changes the value pointed to.
Make all std::shared_ptr<Foo> in your code std::shared_ptr<Foo const>.
This includes the equals and hash code and unordered set code.
auto empl = fooSet.emplace(std::make_shared<Foo>());
(*(empl.first))->bar.emplace_back(0);
this code is right out, and it will (afterwards) fail to compile, as is safe.
If you want to mutate an element in a fooSet,
template<class C, class It, class F>
void mutate(C& c, It it, F&& f) {
auto e = *it->first;
f(e); // do this before erasing, more exception-safe
auto new_elem = std::make_shared<decltype(e)>(std::move(e));
c.erase(it);
c.insert( new_elem ); // could throw, but hard to avoid.
}
now the code reads:
auto empl = fooSet.emplace(std::make_shared<Foo>());
mutate(fooSet, empl.first, [&](auto&& elem) {
elem.emplace_back(0);
});
mutate copies an element out, removes the pointer from the set, calls the function on it, then reinserts it back into the fooSet.
Of course in this case it is dumb; we just put it in and now we take it out mutate it and put it back.
But in a more general case it will be less dumb.
Here you add an object and it's stored using its current hash value.
auto empl = fooSet.emplace(std::make_shared<Foo>());
Here you change the hash value:
(*(empl.first))->bar.emplace_back(0);
The set now has an object stored using the old/wrong hash value. If you need to change anything in an object that affects its hash value, you need to extract the object, change it and re-insert it. If all mutable members of the class are used to calculate the hash value, make it a set of <const Foo> instead.
To make future declarations of sets of shared_ptr<const Foo> easier, you may also extend the std namespace with your specializations.
class Foo {
public:
Foo() {}
size_t hash() const {
size_t seed = 0;
for (auto& b : bar) {
boost::hash_combine(seed, b);
}
return seed;
}
bool operator==(Foo const & rhs) const {
return bar == rhs.bar;
}
std::vector<int> bar;
};
namespace std {
template<>
struct hash<Foo> {
size_t operator()(const Foo& foo) const {
return foo.hash();
}
};
template<>
struct hash<std::shared_ptr<const Foo>> {
size_t operator()(const std::shared_ptr<const Foo>& foo) const {
/* A version using std::hash<Foo>:
std::hash<Foo> hasher;
return hasher(*foo);
*/
return foo->hash();
}
};
template<>
struct equal_to<std::shared_ptr<const Foo>> {
bool operator()(std::shared_ptr<const Foo> const & rhs,
std::shared_ptr<const Foo> const & lhs) const {
return *lhs == *rhs;
}
};
}
With that in place, you can simply declare your unordered_set like this:
std::unordered_set<std::shared_ptr<const Foo>> fooSet;
which now is the same as declaring it like this:
std::unordered_set<
std::shared_ptr<const Foo>,
std::hash<std::shared_ptr<const Foo>>,
std::equal_to<std::shared_ptr<const Foo>>
> fooSet;
I wonder how is the most convenient way to have a sorted set, a set of pointers to objects, like
std::set<myClass*> mySet;
I want this set to be sorted by myClass::someProperty (say, an int).
Should I overload operator < in myClass? I'm not sure if it will work, because it's not a set of myClass, but a set of pointers.
How can I define a compare function?
Thank you very much.
You need to define a type (or a function) that dereferences the pointers and compares the attributes of the objects they point at, something on this general order:
class myClass {
int value;
public:
myClass(int i = 0) : value(i) {}
struct cmp {
bool operator()(myClass *const &a, myClass *const &b) const {
return a->value < b->value;
}
};
};
We they define the set something like this:
std::set<myClass*, myClass::cmp> mySet;
My advice, however, would be to store objects instead of pointers (if possible).
You can also specialize std::less for your myClass* as given below and then no need to pass comparator while creating set:
namespace std {
template<>
struct less<myClass*>
{
bool operator()(const myClass* k1, const myClass* k2) const
{
// Some code ...
}
};
}
I have the following code:
#include <map>
using namespace std;
struct A {};
map</*const*/ A *, int> data;
int get_attached_value(const A *p) {
return data.at(p);
}
void reset_all() {
for (const auto &p : data) *p.first = A();
}
My problem is that this code fails on a type error both when I comment and uncomment the const in the type of data. Is there any way I can solve this without using const_cast and without losing the const in get_attached_value?
The problem seems to be in the pointee type, which has to be the same in both pointer declarations (map key type and the get_attached_value's argument).
OP's code uses const A*, which is a pointer to a const instance of class A (an alternative spelling is A const *). Leaving this const in both map declaration and in get_attached_value' argument almost works, but reset_all does not allow you to assign a new value to *p.first, because the resulting type is A const& (which cannot be assigned into).
Removing both consts works as well, but OP wants to keep a const in get_attached_value.
One solution for OP's requirements, keeping as many consts as possible, seems to be to change the pointer type to a const pointer to a non-const instance of A. This will keep reset_all working, while allowing to use a const pointer in both map declaration and get_attached_value's argument:
#include <map>
using namespace std;
struct A {};
map<A * const, int> data;
int get_attached_value(A * const p) {
return data.at(p);
}
void reset_all() {
for (const auto &p : data)
*p.first = A();
}
Another possible solution, with map's key as non-const but the get_attached_value's parameter const, could use std::lower_bound with a custom comparator to replace the data.at() call:
#include <map>
#include <algorithm>
using namespace std;
struct A {};
map<A*, int> data;
int get_attached_value(A const * const p) {
auto it = std::lower_bound(data.begin(), data.end(), p,
[] (const std::pair<A* const, int>& a, A const* const b) {
return a.first < b;
}
);
return it->second;
}
void reset_all() {
for (const auto &p : data)
*p.first = A();
}
However, this solution will be significantly less efficient than one that would use map's native search functions - std::lower_bound uses linear search when input iterators are not random access.
To conclude, the most efficient solution in C++11 or lower would probably use a const pointer as the map's key, and a const_cast in the reset_all function.
A bit more reading about const notation and pointers can be found here.
I have in memory an array of 16 byte wide entries. Each entry consists of two 64 bit integer fields. The entries are in sorted order based upon the numerical value of the first 64 bit integer of each entry. Is it possible to binary search this with the STL without first loading the data into a std::vector?
I have seen that I can use the STL lower_bound() method on plain arrays, but I need it to ignore the second 64 bit field of eachy entry. Is this possible?
You don't need to use std::vector<>, but it is easiest if you get your data into a proper data type first:
#include <cstdint>
struct mystruct
{
std::int64_t first, second;
};
Your question is unclear as to the way you're storing this data now, but I'm assuming it's something like the above.
Then you can either overload operator< for your data type:
#include <algorithm>
bool operator <(mystruct const& ms, std::int64_t const i)
{
return ms.first < i;
}
int main()
{
mystruct mss[10] = { /*populate somehow*/ };
std::int64_t search_for = /*value*/;
mystruct* found = std::lower_bound(mss, mss + 10, search_for);
}
Or you can define a custom comparator and pass that to std::lower_bound:
#include <algorithm>
struct mystruct_comparer
{
bool operator ()(mystruct const& ms, std::int64_t const i) const
{
return ms.first < i;
}
};
int main()
{
mystruct mss[10] = { /*populate somehow*/ };
std::int64_t search_for = /*value*/;
mystruct* found = std::lower_bound(mss,
mss + 10,
search_for,
mystruct_comparer());
}
Naturally, in C++11, a lambda can be used instead of a full-fledged functor for the comparator.
struct Foo {
int64_t lower;
int64_t upper;
};
Foo arr[N];
Foo f;
f.lower = 42;
auto it = std::lower_bound(arr, arr + N, f,
[](const Foo& lhs, const Foo& rhs){ return lhs.lower < rhs.lower; });
Yes, it's possible. You need to create a class which satisfies the requirements for a ForwardIterator that iterates your elements in the proper way (a pointer of a 16 byte structure will probably do the trick). Then you need to define your own Compare to compare elements ignoring the second 64 bit field.
more info.
template <class ForwardIterator, class T, class Compare>
ForwardIterator lower_bound ( ForwardIterator first, ForwardIterator last,
const T& value, Compare comp );