I'm trying to use an unordered_map for a custom type. However, the map is storing duplicate entries, which have the same hash value and should evaluate as equal when using ==.
I've reduced my code to the following proof of concept, where I can see that the hash function runs correctly, but the equals operator is never called.
#include <unordered_map>
// Define a class with a single integer member.
class Example
{
public: int x;
public: Example(int x)
{
this->x = x;
}
// Overload == and compare the single member.
public: bool operator==(const Example &other) const
{
std::cout << "Comparing two objects\n";
return this->x == other.x;
}
};
// Define a hash function class
class ExampleHash
{
public: size_t operator()(const Example* key) const
{
// simply return the member variable as the hash value.
std::cout << "Returning hash value " << key->x << "\n";
return key->x;
}
};
int main()
{
// Create an empty map.
std::unordered_map<Example*, int, ExampleHash> m;
std::cout << "Inserting a new key\n";
// Insert an object with the value 1.
m[new Example(1)] = 1;
std::cout << "Existing hashes:\n";
ExampleHash fn;
for (auto const &item : m) {
size_t h = fn(item.first);
std::cout << " " << h << ", ";
}
std::cout << "\n";
std::cout << "Finding the key\n";
// Check if the object is in the map.
std::cout << ((m.find(new Example(1)) != m.end()) ? "Found" : "Not found") << "\n";
}
Output:
Inserting a new key
Returning hash value 1
Existing hashes:
Returning hash value 1
1,
Finding the key
Returning hash value 1
Not found
(Note the absence of the "Comparing two objects" line when calling unordered_map::find, despite the hash value clearly being in the map already.)
Pointers are not the objects they point to.
You are using pointers as keys. The objects equal operator will be ignored.
Related
How can a vector of objects and a single object be iterated over as-if they were the same range, without any copying or moving, in range-v3?
One possibility is to cast the single value to a std::array<Object, 1> and then concatenate this array with the vector of objects via range::views::concat:
Improvised solution
#include <array>
#include <iostream>
#include <range/v3/view/concat.hpp>
struct Object {
int value = 0;
Object() { std::cout << "Default " << value << "\n"; }
Object( int value) : value(value) { std::cout << "Value " << value << "\n"; }
Object(Object const& object) : value(object.value) { std::cout << "Copy " << value << "\n"; }
Object(Object && object) : value(std::move(object.value)) { std::cout << "Move " << value << "\n"; }
};
int main() {
// Prints "Value 0"
std::array<Object, 1> objects {0};
// Prints "Value 1"
Object object {1};
for(Object const& object : ranges::views::concat(objects, *reinterpret_cast<std::array<Object, 1>*>(&object))) {
// Prints "0 1"
std::cout << object.value << " ";
}
return 0;
}
I don't know if the cast is safe. Assuming it is, developers are at risk of having to spend time to verify this. Hence this improvised solution is wanting.
Question: Is there an elegant solution for concatenating a view and a single value without copying or moving data?
I am aware of range::views::single but this causes object to be copied once and moved thrice when used in place of reinterpret_cast in the improvised solution.
#include<boost/unordered_map.hpp>
#include<string>
#include<iostream>
#include<boost/unordered_set.hpp>
using namespace std;
typedef boost::unordered_map<string, boost::unordered_map<string, boost::unordered_set<string>>> nfa;
const boost::unordered_map<string, boost::unordered_set<string>>&
get_second(const std::pair<string,
boost::unordered_map<string, boost::unordered_set<string>>>& p)
{return p.second;}
int main()
{
nfa a;
a["A"]["0"] = {"B", "C"};
a["A"]["1"] = {"B"};
a["B"]["0"] = {"B"};
a["B"]["1"] = {"C"};
cout << "Printing using direct reference" << endl;
for (auto tr_table : a)
{
for (auto tr : tr_table.second)
cout << tr_table.first << " " << tr.first << " " << tr.second.size() << endl;
}
cout << "Printing using function get_second" << endl;
for (auto tr_table : a)
{
for (auto tr : get_second(tr_table))
cout << tr_table.first << " " << tr.first << " " << tr.second.size() << endl;
}
return 0;
}
For the same unordered_map, using tr.second returns the correct number of rows but using get_second returns a new map element with no elements.
What is the reason for this behavior?
I am using g++ 5.3.1 on Ubuntu.
PS: The behavior is same when std::unordered_map is used.
get_second takes a pair of the wrong type, with a non-const key.
Therefore a converted temporary is constructed and you are returning a reference to this temporary.
All bets are off after that.
Your get_second method parameter doesn't match the loop iterator in terms of constness... update as follows (note const string in pair) and it works:
get_second( const std::pair<const string,
unordered_map<string, unordered_set<string>>>& p )
Be noted that std::unordered_map's value_type is std::pair<const Key, T> (it's const Key), so your get_second()'s parameter is wrong.
You can simply change to get_second(const nfa::value_type& p) to get correct behavior.
My question is that of safety. I've searched cplusplus.com and cppreference.com and they seem to be lacking on iterator safety during std::move. Specifically: is it safe to call std::unordered_map::erase(iterator) with an iterator whose object has been moved? Sample code:
#include <unordered_map>
#include <string>
#include <vector>
#include <iostream>
#include <memory>
class A {
public:
A() : name("default ctored"), value(-1) {}
A(const std::string& name, int value) : name(name), value(value) { }
std::string name;
int value;
};
typedef std::shared_ptr<const A> ConstAPtr;
int main(int argc, char **argv) {
// containers keyed by shared_ptr are keyed by the raw pointer address
std::unordered_map<ConstAPtr, int> valued_objects;
for ( int i = 0; i < 10; ++i ) {
// creates 5 objects named "name 0", and 5 named "name 1"
std::string name("name ");
name += std::to_string(i % 2);
valued_objects[std::make_shared<A>(std::move(name), i)] = i * 5;
}
// Later somewhere else we need to transform the map to be keyed differently
// while retaining the values for each object
typedef std::pair<ConstAPtr, int> ObjValue;
std::unordered_map<std::string, std::vector<ObjValue> > named_objects;
std::cout << "moving..." << std::endl;
// No increment since we're using .erase() and don't want to skip objects.
for ( auto it = valued_objects.begin(); it != valued_objects.end(); ) {
std::cout << it->first->name << "\t" << it->first.value << "\t" << it->second << std::endl;
// Get named_vec.
std::vector<ObjValue>& v = named_objects[it->first->name];
// move object :: IS THIS SAFE??
v.push_back(std::move(*it));
// And then... is this also safe???
it = valued_objects.erase(it);
}
std::cout << "checking... " << named_objects.size() << std::endl;
for ( auto it = named_objects.begin(); it != named_objects.end(); ++it ) {
std::cout << it->first << " (" << it->second.size() << ")" << std::endl;
for ( auto pair : it->second ) {
std::cout << "\t" << pair.first->name << "\t" << pair.first->value << "\t" << pair.second << std::endl;
}
}
std::cout << "double check... " << valued_objects.size() << std::endl;
for ( auto it : valued_objects ) {
std::cout << it.first->name << " (" << it.second << ")" << std::endl;
}
return 0;
}
The reason I ask is that it strikes me that moving the pair from the unordered_map's iterator may (?) therefore *re*move the iterator's stored key value and therefore invalidate its hash; therefore any operations on it afterward could result in undefined behavior. Unless that's not so?
I do think it's worth noting that the above appears to successfully work as intended in GCC 4.8.2 so I'm looking to see if I missed documentation supporting or explicitly not supporting the behavior.
// move object :: IS THIS SAFE??
v.push_back(std::move(*it));
Yes, it is safe, because this doesn't actually modify the key. It cannot, because the key is const. The type of *it is std::pair<const ConstAPtr, int>. When it is moved, the first member (the const ConstAPtr) is not actually moved. It is converted to an r-value by std::move, and becomes const ConstAPtr&&. But that doesn't match the move constructor, which expects a non-const ConstAPtr&&. So the copy constructor is called instead.
I'd like to simulate a std::vector that has mixed const and non-const elements. More specifically, I want to have functions that operate on a vector and are allowed to see the entire vector but may only write to specific elements. The elements that can and cannot be written will be determined at runtime and may change during runtime.
One solution is to create a container that holds an array of elements and an equal sized array of booleans. All non-const access would be through a function that checks against the boolean array if the write is valid and throws an exception otherwise. This has the downside of adding a conditional to every write.
A second solution might be to have the same container but this time write access is done by passing an array editing function to a member function of the container. The container member function would let the array editing function go at the array and then check that it didn't write to the non-writable elements. This has the downside that the array editing function could be sneaky and pass around non-const pointers to the array elements, let the container function check that all is well, and then write to non-writable elements.
The last issue seems difficult to solve. It seems like offering direct writable access ever means we have to assume direct writable access always.
Are there better solutions?
EDIT: Ben's comment has a good point I should have addressed in the question: why not a vector of const and a vector of non-const?
The issue is that the scenario I have in mind is that we have elements that are conceptually part of one single array. Their placement in that array is meaningful. To use vectors of const and non-const requires mapping the single array that exist in concept to the two vectors that would implement it. Also, if the list of writable elements changes then the elements or pointers in the two vectors would need to be moved about.
I think you can accomplish what you wish with the following class, which is very simplified to illustrate the main concept.
template <typename T>
struct Container
{
void push_back(bool isconst, T const& item)
{
data.push_back(std::make_pair(isconst, item));
}
T& at(size_t index)
{
// Check whether the object at the index is const.
if ( data[index].first )
{
throw std::runtime_error("Trying to access a const-member");
}
return data[index].second;
}
T const& at(size_t index) const
{
return data[index].second;
}
T const& at(size_t index, int dummy) // Without dummy, can't differentiate
// between the two functions.
{
return data[index].second;
}
T const& at(size_t index, int dummy) const // Without dummy, can't differentiate
// between the two functions.
{
return data[index].second;
}
std::vector<std::pair<bool, T> > data;
};
Here's a test program and its output.
#include <stdio.h>
#include <iostream>
#include <utility>
#include <stdexcept>
#include <vector>
//--------------------------------
// Put the class definition here.
//--------------------------------
int main()
{
Container<int> c;
c.push_back(true, 10);
c.push_back(false, 20);
try
{
int value = c.at(0); // Show throw exception.
}
catch (...)
{
std::cout << "Expected to see this.\n";
}
int value = c.at(0, 1); // Should work.
std::cout << "Got c[0]: " << value << "\n";
value = c.at(1); // Should work.
std::cout << "Got c[1]: " << value << "\n";
value = c.at(1, 1); // Should work.
std::cout << "Got c[1]: " << value << "\n";
// Accessing the data through a const object.
// All functions should work since they are returning
// const&.
Container<int> const& cref = c;
value = cref.at(0); // Should work.
std::cout << "Got c[0]: " << value << "\n";
value = cref.at(0, 1); // Should work.
std::cout << "Got c[0]: " << value << "\n";
value = cref.at(1); // Should work.
std::cout << "Got c[1]: " << value << "\n";
value = cref.at(1, 1); // Should work.
std::cout << "Got c[1]: " << value << "\n";
// Changing values ... should only work for '1'
try
{
c.at(0) = 100; // Show throw exception.
}
catch (...)
{
std::cout << "Expected to see this.\n";
}
c.at(1) = 200; // Should work.
std::cout << "Got c[1]: " << c.at(1) << "\n";
}
Output from running the program:
Expected to see this.
Got c[0]: 10
Got c[1]: 20
Got c[1]: 20
Got c[0]: 10
Got c[0]: 10
Got c[1]: 20
Got c[1]: 20
Expected to see this.
Got c[1]: 200
I develop one program in c++ in which i have to find key in stl map by using values.
But values assigned to key is the 5 tuples (srcip,port,destip,port,srcno)
Now i want to check in map whether there is key assosiated with values.
I am trying something like this.
But its showing error like
wrong number of template argument.
Note(In my program in pair key->Value) value consist of tuple of 5 variable.
template<class T>
struct map_data_compare : public std::binary_function<typename T::value_type,typename T::mapped_type,bool>
{
public:
bool operator() (typename T::value_type &pair,typename T::mapped_type i)
{
return pair.second == i;
}
}
class Values
{
private:
std::string C_addr;
int C_port;
std::string S_addr;
int S_port;
int C_ID;
public:
Values(std::string,int,std::string,int,int);
void printValues();
};
Values :: Values(std::string Caddr,int Cport,std::string Saddr,int Sport,int Cid)
{
C_addr=Caddr;
C_port=Cport;
S_addr=Saddr;
S_port=Sport;
C_ID=Cid;
}
void Values::printValues()
{
cout << C_addr<<":" <<C_port<<":" << S_addr <<":" <<S_port << ":"<<C_ID <<endl;
}
//In main
{
typedef std::map<int, Values> itemsType;
itemsType items;
Values connection (inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port),inet_ntoa(servaddr.sin_addr),ntohs(servaddr.sin_port),clientID);
std::map<std::int,Values>::iterator it = std::find_if( items.begin(), items.end(), std::bind2nd(map_data_compare<itemsType>(),connection));
if ( it != items.end() )
{
assert( connection == it->second);
std::cout << "Found index:" << it->first << " for values:" << it->second << std::endl;
}
else
{
std::cout << "Did not find index for values:" << connection <<endl;
}
I develop one program in c++ in which i have to find key in stl map by using values.
That's not what maps are meant for. If you need that kind of access, I recommend Boost.Bimap
If the 'key' must be unique, maybe you can try combine the key and value into a std::pair and push them into std::set.
Otherwise you should set your key as value and value as key since you seems mainly use your original value as what we treat to a "key". Then you could use the built-in map::find() function