Reputation: 283614
Let's say I have a std::map<std::string, T>
(or unordered_map
) and I want to access the key from an iterator/reference/pointer to the content.
Is there a way to do that without having two copies of the std::string
key (one owned by the map, one inside the content object)? Can one be a reference to the other?
Upvotes: 7
Views: 1565
Reputation: 66922
The reason they made this hard is because it's dangerous. You have to GUARANTEE that none of the std::string
members being key'd off of will never change value, or the whole map becomes invalidated. Interesting, the first solution that comes to mind appears insanely hackish, and looks like UB, but I believe I very carefully skirt the UB.
struct key_type {
mutable const char* ptr;
};
bool operator<(const key_type& lhs, const key_type& rhs)
{return strcmp(lhs.ptr, rhs.ptr)<0;}
struct person {
std::string name;
int age;
};
person& people_map_get(std::map<key_type, person>& map, const char* name) {
auto it = map.insert(name, person{name}).first; //grab, possibly insert
if->first.ptr = it->second.name.c_str(); //in case of insert, fix ptr
return it->second;
}
person& people_map_assign(std::map<key_type, person>& map, person p) {
auto pair = map.insert(name, p); //grab, possibly insert
auto it = pair.first;
if (pair.second == false)
it->second = std::move(p);
if->first.ptr = it->second.name.c_str(); //ptr probably invalidated, so update it
return it->second;
}
int main() {
std::map<key_type, person> people;
people_map_assign(people, person{"ted"});
person frank = people_map_get(people, "frank");
}
As I hope is clear, this is crazy close to UB, and very much not recommended. Basically, during an insert/find, the key points at your temporary object or input string, and then as soon as the object is inserted/found, the key is changed to point at the value contained in the string member, and as long as you never do anything that would invalidate the return value of .c_str()
on any contained person
object, everything just barely works. I think.
Upvotes: 2
Reputation: 2985
Would you consider using boost::bimap? Below is a simple example:
#include <boost/bimap.hpp>
#include <string>
struct Person
{
Person()
{}
Person(const std::string& f, const std::string& l, int a) : first(f), last(l), age(a)
{}
std::string first;
std::string last;
int age;
};
bool operator <(const Person& lhs, const Person& rhs)
{
if(lhs.last < rhs.last)
return true;
return false;
}
std::ostream& operator << (std::ostream& os, const Person& p)
{
os << "First Name: " << p.first << " Last Name: " << p.last << " Age: " << p.age;
return os;
}
int main()
{
typedef boost::bimap<std::string, Person> people;
typedef people::value_type value;
people m;
m.insert(value("12345",Person("fred", "rabbit", 10)));
m.insert(value("67890",Person("benjamin", "bunny", 12)));
Person p = m.left.at("12345");
std::cout << "Person with serial no. 12345 is: " << p << "\n";
std::cout << "Serial number of " << p << " is: " << m.right.at(p) << "\n";
}
Upvotes: 3
Reputation: 1257
Why don't you create two objects:
std::set<std::string> wordSet;
std::map<std::string*, T> yourMap;
T must contain pointer to std::string, and yourMap needs custom comparator. Additionally you can wrap all these in some class.
Upvotes: 1
Reputation: 5155
Both can be a reference to the same value. For example:
#include <stdio.h>
#include <string>
#include <map>
struct xx { std::string mykey; int value; };
int main (int argc, char **argv)
{
std::string key1("the-key");
std::map<std::string, xx> map;
map[key1].mykey = key1;
map[key1].value = 13;
std::string &lookup_key = map[key1].mykey;
printf("%d\n", map[lookup_key].value);
}
Upvotes: -1