Avoiding multiple lookups in map/unordered_map - c++

Let's say we have an expensive function mapping string to int and want to cache results in a map.
The simplest code would be
int mapStringToIntWithCache(std::string const& s) {
static std::unordered_map<std::string, int> cache;
if (cache.count(s) > 0) return cache[s];
else return cache[s] = myExpensiveFunction(s);
}
But this has 2 lookups.
I therefore tend to write this
int mapStringToIntWithCache(std::string const& s) {
static std::unordered_map<std::string, int> cache;
size_t sizeBefore = cache.size();
int& val = cache[s];
if (cache.size() > sizeBefore) val = myExpensiveFunction(s);
return val;
}
This has only one lookup, but seems a little clumsy. Is there a better way?

Just use std::map::emplace() method:
int mapStringToIntWithCache(std::string const& s) {
static std::unordered_map<std::string, int> cache;
auto pair = cache.emplace( s, 0 );
if( pair.second )
pair.first->second = myExpensiveFunction(s);
return pair.first->second;
}

Just a note to the #Slava's answer: If you pass argument by const lvalue reference, you cannot move then from this argument if it's rvalue:
int i = mapStringToIntWithCache("rvalue argument here");
The temporary std::string argument will be copied here if insertion to cache takes place.
You can use perfect forwarding, however, if you want to maintain arguments to be of std::string type only (e.g., for implicit conversions from string literals), then you need some wrapper-helper function solution:
template <typename T>
int mapStringToIntWithCacheHelper(T&& s) {
static std::unordered_map<std::string, int> cache;
auto pair = cache.emplace( std::forward<T>(s), 0 );
if( pair.second )
pair.first->second = myExpensiveFunction(pair.first->first); // can't use s here !!!
return pair.first->second;
}
int mapStringToIntWithCache(const std::string & s) {
mapStringToIntWithCacheHelper(s);
}
int mapStringToIntWithCache(std::string && s) {
mapStringToIntWithCacheHelper(std::move(s));
}

Related

Get byte representation of C++ class

I have objects that I need to hash with SHA256. The object has several fields as follows:
class Foo {
// some methods
protected:
std::array<32,int> x;
char y[32];
long z;
}
Is there a way I can directly access the bytes representing the 3 member variables in memory as I would a struct ? These hashes need to be computed as quickly as possible so I want to avoid malloc'ing a new set of bytes and copying to a heap allocated array. Or is the answer to simply embed a struct within the class?
It is critical that I get the exact binary representation of these variables so that the SHA256 comes out exactly the same given that the 3 variables are equal (so I can't have any extra padding bytes etc included going into the hash function)
Most Hash classes are able to take multiple regions before returning the hash, e.g. as in:
class Hash {
public:
void update(const void *data, size_t size) = 0;
std::vector<uint8_t> digest() = 0;
}
So your hash method could look like this:
std::vector<uint8_t> Foo::hash(Hash *hash) const {
hash->update(&x, sizeof(x));
hash->update(&y, sizeof(y));
hash->update(&z, sizeof(z));
return hash->digest();
}
You can solve this by making an iterator that knows the layout of your member variables. Make Foo::begin() and Foo::end() functions and you can even take advantage of range-based for loops.
If you can increment it and dereference it, you can use it any other place you're able to use a LegacyForwardIterator.
Add in comparison functions to get access to the common it = X.begin(); it != X.end(); ++it idiom.
Some downsides include: ugly library code, poor maintainability, and (in this current form) no regard for endianess.
The solution to the latter downside is left as an exercise to the reader.
#include <array>
#include <iostream>
class Foo {
friend class FooByteIter;
public:
FooByteIter begin() const;
FooByteIter end() const;
Foo(const std::array<int, 2>& x, const char (&y)[2], long z)
: x_{x}
, y_{y[0], y[1]}
, z_{z}
{}
protected:
std::array<int, 2> x_;
char y_[2];
long z_;
};
class FooByteIter {
public:
FooByteIter(const Foo& foo)
: ptr_{reinterpret_cast<const char*>(&(foo.x_))}
, x_end_{reinterpret_cast<const char*>(&(foo.x_)) + sizeof(foo.x_)}
, y_begin_{reinterpret_cast<const char*>(&(foo.y_))}
, y_end_{reinterpret_cast<const char*>(&(foo.y_)) + sizeof(foo.y_)}
, z_begin_{reinterpret_cast<const char*>(&(foo.z_))}
{}
static FooByteIter end(const Foo& foo) {
FooByteIter fbi{foo};
fbi.ptr_ = reinterpret_cast<const char*>(&foo.z_) + sizeof(foo.z_);
return fbi;
}
bool operator==(const FooByteIter& other) const { return ptr_ == other.ptr_; }
bool operator!=(const FooByteIter& other) const { return ! (*this == other); }
FooByteIter& operator++() {
ptr_++;
if (ptr_ == x_end_) {
ptr_ = y_begin_;
}
else if (ptr_ == y_end_) {
ptr_ = z_begin_;
}
return *this;
}
FooByteIter operator++(int) {
FooByteIter pre = *this;
(*this)++;
return pre;
}
char operator*() const {
return *ptr_;
}
private:
const char* ptr_;
const char* const x_end_;
const char* const y_begin_;
const char* const y_end_;
const char* const z_begin_;
};
FooByteIter Foo::begin() const {
return FooByteIter(*this);
}
FooByteIter Foo::end() const {
return FooByteIter::end(*this);
}
template <typename InputIt>
char checksum(InputIt first, InputIt last) {
char check = 0;
while (first != last) {
check += (*first);
++first;
}
return check;
}
int main() {
Foo f{{1, 2}, {3, 4}, 5};
for (const auto b : f) {
std::cout << (int)b << ' ';
}
std::cout << std::endl;
std::cout << "Checksum is: " << (int)checksum(f.begin(), f.end()) << std::endl;
}
You can generalize this further by making serialization functions for all data types you might care about, allowing serialization of classes that aren't plain-old-data types.
Warning
This code assumes that the underlying types being serialized have no internal padding, themselves. This answer works for this datatype because it is made of types which themselves do not pad. To make this work for datatypes that have datatypes that have padding, this method would need to be recursed all the way down.
Just cast a pointer to object to a pointer to char. You can iterate through the bytes by increment. Use sizeof(foo) to check overflow.
As long as you're able to make your class an aggregate, i.e. std::is_aggregate_v<T> == true, you can actually sort-of reflect the members of the structure.
This allows you to easily hash the members without actually having to name them. (also you don't have to remember updating your hash function every time you add a new member)
Step 1: Getting the number of members inside the aggregate
First we need to know how many members a given aggregate type has.
We can check this by (ab-)using aggregate initialization.
Example:
Given struct Foo { int i; int j; };:
Foo a{}; // ok
Foo b{{}}; // ok
Foo c{{}, {}}; // ok
Foo d{{}, {}, {}}; // error: too many initializers for 'Foo'
We can use this to get the number of members inside the struct, by trying to add more initializers until we get an error:
template<class T>
concept aggregate = std::is_aggregate_v<T>;
struct any_type {
template<class T>
operator T() {}
};
template<aggregate T>
consteval std::size_t count_members(auto ...members) {
if constexpr (requires { T{ {members}... }; } == false)
return sizeof...(members) - 1;
else
return count_members<T>(members..., any_type{});
}
Notice that i used {members}... instead of members....
This is because of arrays - a structure like struct Bar{int i[2];}; could be initialized with 2 elements, e.g. Bar b{1, 2}, so our function would have returned 2 for Bar if we had used members....
Step 2: Extracting the members
Now that we know how many members our structure has, we can use structured bindings to extract them.
Unfortunately there is no way in the current standard to create a structured binding expression with a variable amount of expressions, so we have to add a few extra lines of code for each additional member we want to support.
For this example i've only added a max of 4 members, but you can add as many as you like / need:
template<aggregate T>
constexpr auto tie_struct(T const& data) {
constexpr std::size_t fieldCount = count_members<T>();
if constexpr(fieldCount == 0) {
return std::tie();
} else if constexpr (fieldCount == 1) {
auto const& [m1] = data;
return std::tie(m1);
} else if constexpr (fieldCount == 2) {
auto const& [m1, m2] = data;
return std::tie(m1, m2);
} else if constexpr (fieldCount == 3) {
auto const& [m1, m2, m3] = data;
return std::tie(m1, m2, m3);
} else if constexpr (fieldCount == 4) {
auto const& [m1, m2, m3, m4] = data;
return std::tie(m1, m2, m3, m4);
} else {
static_assert(fieldCount!=fieldCount, "Too many fields for tie_struct! add more if statements!");
}
}
The fieldCount!=fieldCount in the static_assert is intentional, this prevents the compiler from evaluating it prematurely (it only complains if the else case is actually hit)
Now we have a function that can give us references to each member of an arbitrary aggregate.
Example:
struct Foo {int i; float j; std::string s; };
Foo f{1, 2, "miau"};
// tup is of type std::tuple<int const&, float const&, std::string const&>
auto tup = tie_struct(f);
// this will output "12miau"
std::cout << std::get<0>(tup) << std::get<1>(tup) << std::get<2>(tup) << std::endl;
Step 3: hashing the members
Now that we can convert any aggregate into a tuple of its members, hashing it shouldn't be a big problem.
You can basically hash the individual types like you want and then combine the individual hashes:
// for merging two hash values
std::size_t hash_combine(std::size_t h1, std::size_t h2)
{
return (h2 + 0x9e3779b9 + (h1<<6) + (h1>>2)) ^ h1;
}
// Handling primitives
template <class T, class = void>
struct is_std_hashable : std::false_type { };
template <class T>
struct is_std_hashable<T, std::void_t<decltype(std::declval<std::hash<T>>()(std::declval<T>()))>> : std::true_type { };
template <class T>
concept std_hashable = is_std_hashable<T>::value;
template<std_hashable T>
std::size_t hash(T value) {
return std::hash<T>{}(value);
}
// Handling tuples
template<class... Members>
std::size_t hash(std::tuple<Members...> const& tuple) {
return std::apply([](auto const&... members) {
std::size_t result = 0;
((result = hash_combine(result, hash(members))), ...);
return result;
}, tuple);
}
template<class T, std::size_t I>
using Arr = T[I];
// Handling arrays
template<class T, std::size_t I>
std::size_t hash(Arr<T, I> const& arr) {
std::size_t result = 0;
for(T const& elem : arr) {
std::size_t h = hash(elem);
result = hash_combine(result, h);
}
return result;
};
// Handling structs
template<aggregate T>
std::size_t hash(T const& agg) {
return hash(tie_struct(agg));
}
This allows you to hash basically any aggregate struct, even with arrays and nested structs:
struct Foo{ int i; double d; std::string s; };
struct Bar { Foo k[10]; float f; };
std::cout << hash(Foo{1, 1.2f, "miau"}) << std::endl;
std::cout << hash(Bar{}) << std::endl;
full example on godbolt
Footnotes
This only works with aggregates
No need to worry about padding because we access the members directly.
You have to add a few more ifs into tie_struct if you need more than 4 members
The provided hash() function doesn't handle all types - if you need e.g. std::array, std::pair, etc... you need to add overloads for those.
It's a lot of boilerplate code, but it's insanely powerful.
You can also use Boost.PFR for the aggregate-to-tuple part, if you are allowed to use boost

Tuple bool value does not change

I have a small problem to change the tuple bool value. Does anyone know how the bool value is not changed? The function setState() finds the searched key! Many thanks for your help!
keyManager.h
class keyManager
{
private:
std::vector<std::tuple<std::string, bool>> productKeys;
public:
void addProductKey(std::string key);
std::tuple<std::string, bool> getProductKey(int index);
void setState(std::string searchKey, bool state);
keyManager();
~keyManager();
};
keyManager.cpp
void keyManager::addProductKey(std::string key)
{
productKeys.emplace_back(key, false);
}
std::tuple<std::string, bool> keyManager::getProductKey(int index)
{
return productKeys[index];
}
void keyManager::setState(std::string searchKey, bool state)
{
for (int x = 0; x < productKeys.size(); x++)
{
auto t = productKeys[x];
if (std::get<std::string>(t) == searchKey)
{
std::get<bool>(t) = state;
}
}
}
main()
keyManager kManager;
kManager.addProductKey(KEY_1);
kManager.setState(KEY_1, true);
auto t = kManager.getProductKey(0);
std::cout << std::get<bool>(t) << std::endl;
Output: 0
The program is executed without errors, so I assume that I have a mistake somewhere happened.
In your keyManager::setState the line
auto t = productKeys[x];
makes a copy, use
auto& t = productKeys[x];
instead to get a reference to productKeys[x].
This line will copy your tuple
auto t = productKeys[x];
Switch to a reference
auto& t = productKeys[x];
The problem is at this line:
auto t = productKeys[x];
t is deduced to be of type std::tuple<std::string, bool> and the = triggers the copy asssignment operator of the tuple which actually makes t a copy of productKeys[x].
All the operation you perform on t affect only t.
You should force the compiler to deduce t as a reference (with type std::tuple<std::string, bool>&) as in the following:
auto& t = productKeys[x];

C++ Math Parser with user-defined function

I want to implement a math parser with user-defined function.
There are several problems to be solved.
For example, int eg(int a,int b){return a+b;} is the function I want to add to the parser.
First: How to store all the functions into a container?
std::map<std::string,boost::any> func_map may be a choose (by func_map["eg"]=eg". However, It's very hard to call the function in this kind of map, for I have to use any_cast<T> to get the real function from the wrapper of boost::any.
Second: How to handle the overloaded function?
It's true that I can distinguish the overloaded functions by the method of typeid, but it's far from a real implementation.
Parsering expressions is not a difficult skill and the hardest part is described above.
muparserx provides an interesting solution for this problem, but I'm finding another method.
I'm not familiar with lambda expressions but may be it's an acceptable way.
Update:
I need something like this:
int eg(int a,int b){ return a+b;}
int eg(int a,int b, string c){return a+b+c.length();}
double eh(string a){return length.size()/double(2);}
int main(){
multimap<string,PACKED_FUNC> func_map;
func_map.insert(make_pair("eg",pack_function<int,int>(eg));
func_map.insert(make_pair("eg",pack_function<int,int,string>(eg));
func_map.insert(make_pair("eh",pack_function<string>(eh));
auto p1=make_tuple(1,2);
int result1=apply("eg",PACK_TUPLE(p1));//result1=3
auto p2=tuple_cat(p1,make_tuple("test"));
int result2=apply("eg",PACK_TUPLE(p2));//result2=7
auto p3=make_tuple("testagain");
double result3=apply("eh",PACK_TUPLE(p3));//result3=4.5
return 0;
}
How to store all the functions into a container?
To store then inside some container, they must be of the same type. The std::function wrapper is a good choice, since this allows you to use even stateful function objects. Since you probably don't want all functions to take the same number of arguments, you need to "extract" the arity of the functions from the static host type system. An easy solution is to use functions that accept a std::vector:
// Arguments type to the function "interface"
using Arguments = std::vector<int> const &;
// the interface
using Function = std::function<int (Arguments)>;
But you don't want your users to write functions that have to unpack their arguments manually, so it's sensible to automate that.
// Base case of packing a function.
// If it's taking a vector and no more
// arguments, then there's nothing left to
// pack.
template<
std::size_t N,
typename Fn>
Function pack(Fn && fn) {
return
[fn = std::forward<decltype(fn)>(fn)]
(Arguments arguments)
{
if (N != arguments.size()) {
throw
std::string{"wrong number of arguments, expected "} +
std::to_string(N) +
std::string{" but got "} +
std::to_string(arguments.size());
}
return fn(arguments);
};
}
The above code handles the easy case: A function that already accepts a vector. For all other functions they need to be wrapped and packed into a newly created function. Doing this one argument a time makes this relatively easy:
// pack a function to a function that takes
// it's arguments from a vector, one argument after
// the other.
template<
std::size_t N,
typename Arg,
typename... Args,
typename Fn>
Function pack(Fn && fn) {
return pack<N+1, Args...>(
[fn = std::forward<decltype(fn)>(fn)]
(Arguments arguments, Args const &... args)
{
return fn(
arguments,
arguments[N],
args...);
});
}
The above only works with (special) functions that already take a vector. For normal functions we need an function to turn them into such special functions:
// transform a function into one that takes its
// arguments from a vector
template<
typename... Args,
typename Fn>
Function pack_function(Fn && fn) {
return pack<0, Args...>(
[fn = std::forward<decltype(fn)>(fn)]
(Arguments arguments, Args const &... args)
{
return fn(args...);
});
}
Using this, you can pack any function up to be the same type:
Function fn =
pack_function<int, int>([] (auto lhs, auto rhs) {return lhs - rhs;});
You can then have them in a map, and call them using some vector, parsed from some input:
int main(int, char**) {
std::map<std::string, Function> operations;
operations ["add"] = pack_function<int, int>(add);
operations ["sub"] = pack_function<int, int>(
[](auto lhs, auto rhs) { return lhs - rhs;});
operations ["sum"] = [] (auto summands) {
int result = 0;
for (auto e : summands) {
result += e;
}
return result;
};
std::string line;
while (std::getline(std::cin, line)) {
std::istringstream command{line};
std::string operation;
command >> operation;
std::vector<int> arguments {
std::istream_iterator<int>{command},
std::istream_iterator<int>{} };
auto function = operations.find(operation);
if (function != operations.end ()) {
std::cout << line << " = ";
try {
std::cout << function->second(arguments);
} catch (std::string const & error) {
std::cout << error;
}
std::cout << std::endl;
}
}
return 0;
}
A live demo of the above code is here.
How to handle the overloaded function? It's true that I can distinguish the overloaded functions by the method of typeid, but it's far from a real implementation.
As you see, you don't need to, if you pack the relevant information into the function. Btw, typeid shouldn't be used for anything but diagnostics, as it's not guaranteed to return different strings with different types.
Now, finally, to handle functions that don't only take a different number of arguments, but also differ in the types of their arguments, you need to unify those types into a single one. That's normally called a "sum type", and very easy to achieve in languages like Haskell:
data Sum = IVal Int | SVal String
-- A value of type Sum is either an Int or a String
In C++ this is a lot harder to achieve, but a simple sketch could look such:
struct Base {
virtual ~Base() = 0;
};
inline Base::~Base() {}
template<typename Target>
struct Storage : public Base {
Target value;
};
struct Any {
std::unique_ptr<Base const> value;
template<typename Target>
Target const & as(void) const {
return
dynamic_cast<Storage<Target> const &>(*value).value;
}
};
template<typename Target>
auto make_any(Target && value) {
return Any{std::make_unique<Storage<Target>>(value)};
}
But this is only a rough sketch, since there's boost::any which should work perfectly for this case. Note that the above and also boost::any are not quite like a real sum type (they can be any type, not just one from a given selection), but that shouldn't matter in your case.
I hope this gets you started :)
Since you had problems adding multi type support I expanded a bit on the above sketch and got it working. The code is far from being production ready, though: I'm throwing strings around and don't talk to me about perfect forwarding :D
The main change to the above Any class is the use of a shared pointer instead of a unique one. This is only because it saved me from writing copy and move constructors and assignment operators.
Apart from that I added a member function to be able to print an Any value to a stream and added the respective operator:
struct Base {
virtual ~Base() = 0;
virtual void print_to(std::ostream &) const = 0;
};
inline Base::~Base() {}
template<typename Target>
struct Storage : public Base {
Target value;
Storage (Target t) // screw perfect forwarding :D
: value(std::forward<Target>(t)) {}
void print_to(std::ostream & stream) const {
stream << value;
}
};
struct Any {
std::shared_ptr<Base const> value;
template<typename Target>
Target const & as(void) const {
return
dynamic_cast<Storage<Target> const &>(*value).value;
}
template<typename T>
operator T const &(void) const {
return as<T>();
}
friend std::ostream & operator<<(std::ostream& stream, Any const & thing) {
thing.value->print_to(stream);
return stream;
}
};
template<typename Target>
Any make_any(Target && value) {
return Any{std::make_shared<Storage<typename std::remove_reference<Target>::type> const>(std::forward<Target>(value))};
}
I also wrote a small "parsing" function which shows how to turn a raw literal into an Any value containing (in this case) either an integer, a double or a string value:
Any parse_literal(std::string const & literal) {
try {
std::size_t next;
auto integer = std::stoi(literal, & next);
if (next == literal.size()) {
return make_any (integer);
}
auto floating = std::stod(literal, & next);
if (next == literal. size()) {
return make_any (floating);
}
} catch (std::invalid_argument const &) {}
// not very sensible, string literals should better be
// enclosed in some form of quotes, but that's the
// job of the parser
return make_any<std:: string> (std::string{literal});
}
std::istream & operator>>(std::istream & stream, Any & thing) {
std::string raw;
if (stream >> raw) {
thing = parse_literal (raw);
}
return stream;
}
By also providing operator>> it's possible to keep using istream_iterators for input.
The packing functions (or more precisely the functions returned by them) are also modified: When passing an element from the arguments vector to the next function, an conversion from Any to the respective argument type is performed. This may also fail, in which case a std::bad_cast is caught and an informative message rethrown. The innermost function (the lambda created inside pack_function) wraps its result into an make_any call.
add 5 4 = 9
sub 3 2 = 1
add 1 2 3 = wrong number of arguments, expected 2 but got 3
add 4 = wrong number of arguments, expected 2 but got 1
sum 1 2 3 4 = 10
sum = 0
sub 3 1.5 = argument 1 has wrong type
addf 3 3.4 = argument 0 has wrong type
addf 3.0 3.4 = 6.4
hi Pete = Hello Pete, how are you?
An example similar to the previous one can be found here. I need to add that this Any type doesn't support implicit type conversions, so when you have an Any with an int stored, you cannot pass that to an function expecting a double. Though this can be implemented (by manually providing a lot of conversion rules).
But I also saw your update, so I took that code and applied the necessary modifications to run with my presented solution:
Any apply (multimap<string, Function> const & map, string const & name, Arguments arguments) {
auto range = map.equal_range(name);
for (auto function = range.first;
function != range.second;
++function) {
try {
return (function->second)(arguments);
} catch (string const &) {}
}
throw string {" no such function "};
}
int eg(int a,int b){ return a+b;}
int eg(int a,int b, string c){return a+b+c.length();}
double eh(string a){return a.size()/double(2);}
int main(){
multimap<string, Function> func_map;
func_map.insert(make_pair(
"eg",pack_function<int,int>(
static_cast<int(*)(int, int)>(&eg))));
func_map.insert(make_pair(
"eg",pack_function<int,int,string>(
static_cast<int (*)(int, int, string)>(&eg))));
func_map.insert(make_pair(
"eh",pack_function<string>(eh)));
// auto p1=make_tuple(1,2);
// if you want tuples, just write a
// function to covert them to a vector
// of Any.
Arguments p1 =
{make_any (1), make_any (2)};
int result1 =
apply(func_map, "eg", p1).as<int>();
vector<Any> p2{p1};
p2.push_back(make_any<string> ("test"));
int result2 =
apply(func_map, "eg", p2).as<int>();
Arguments p3 = {make_any<string>("testagain")};
double result3 =
apply(func_map, "eh", p3).as<double>();
cout << result1 << endl;
cout << result2 << endl;
cout << result3 << endl;
return 0;
}
It doesn't use tuples, but you could write a (template recursive) function to access each element of a tuple, wrap it into an Any and pack it inside a vector.
Also I'm not sure why the implicit conversion from Any doesn't work when initialising the result variables.
Hm, converting it to use boost::any shouldn't be that difficult. First, the make_any would just use boost::any's constructor:
template<typename T>
boost::any make_any(T&& value) {
return boost::any{std::forward<T>(value)};
}
In the pack function, the only thing that I'd guess needs to be changed is the "extraction" of the correct type from the current element in the arguments vector. Currently this is as simple as arguments.at(N), relying on implicit conversion to the required type. Since boost::any doesn't support implicit conversion, you need to use boost::any_cast to get to the underlying value:
template<
std::size_t N,
typename Arg,
typename... Args,
typename Fn>
Function pack(Fn && fn) {
return pack<N+1, Args...>(
[fn = std::forward<decltype(fn)>(fn)]
(Arguments arguments, Args const &... args)
{
try {
return fn(
arguments,
boost::any_cast<Arg>(arguments.at(N)),
args...);
} catch (boost::bad_any_cast const &) { // throws different type of exception
throw std::string{"argument "} + std::to_string (N) +
std::string{" has wrong type "};
}
});
}
And of course, if you use it like in the example you provided you also need to use boost::any_cast to access the result value.
This should (in theory) do it, eventually you need to add some std::remove_reference "magic" to the template parameter of the boost::any_cast calls, but I doubt that this is neccessary.
(typename std::remove_reference<T>::type instead of just T)
Though I currently cannot test any of the above.

Can a map's key be shared with part of the value?

Can a std::map's or std::unordered_map's key be shared with part of the value? Especially if the key is non-trivial, say like a std::string?
As a simple example let's take a Person object:
struct Person {
// lots of other values
std::string name;
}
std::unordered_map<std::string, std::shared_ptr<Person>> people;
void insertPerson(std::shared_ptr<Person>& p) {
people[p.name] = p;
// ^^^^^^
// copy of name string
}
std::shared_ptr<Person> lookupPerson(const std::string& name) const {
return people[name];
}
My first thought is a wrapper around the name that points to the person, but I cannot figure out how to do a lookup by name.
For your purpose, a std::map can be considered a std::set containing std::pair's which is ordered (and thus efficiently accessible) according to the first element of the pair.
This view is particularly useful if key and value elements are partly identical, because then you do not need to artificially separate value and key elements for a set (and neither you need to write wrappers around the values which select the key).
Instead, one only has to provide a custom ordering function which works on the set and extracts the relevant key part.
Following this idea, your example becomes
auto set_order = [](auto const& p, auto const& s) { return p->name < s->name; };
std::set<std::shared_ptr<Person>, decltype(set_order)> people(set_order);
void insertPerson(std::shared_ptr<Person>& p) {
people.insert(p);
}
As an alternative, here you could also drop the custom comparison and order the set by the addresses in the shared pointer (which supports < and thus can be used directly in the set):
std::set<std::shared_ptr<Person> > people;
void insertPerson(std::shared_ptr<Person>& p) {
people.insert(p);
}
Replace set by unordered_set where needed (in general you then also need to provide a suitable hash function).
EDIT: The lookup can be performed using std:lower_bound:
std::shared_ptr<Person> lookupPerson(std::string const& s)
{
auto comp = [](auto const& p, auto const& s) { return p->name < s; };
return *std::lower_bound(std::begin(people), std::end(people), s, comp);
}
DEMO.
EDIT 2: However, given this more-or-less ugly stuff, you can also follow the lines of your primary idea and use a small wrapper around the value as key, something like
struct PersonKey
{
PersonKey(std::shared_ptr<Person> const& p) : s(p->name) {}
PersonKey(std::string const& _s) : s(_s) {}
std::string s;
bool operator<(PersonKey const& rhs) const
{
return s < rhs.s;
}
};
Use it like (untested)
std::map<PersonKey, std::shared_ptr<Person> > m;
auto sptr = std::make_shared<Person>("Peter");
m[PersonKey(sptr)]=sptr;
Lookup is done through
m[PersonKey("Peter")];
Now I like this better than my first suggestion ;-)
Here's an alternative to davidhigh's answer.
struct Person {
// lots of other values
std::string name;
}
struct StrPtrCmp {
bool operator()(const std::string* a, const std::string* b) const {
return *a < *b;
}
}
std::map<const std::string*, std::shared_ptr<Person>, StrPtrCmp> people();
void insertPerson(std::shared_ptr<Person>& p) {
people[&(p.name)] = p;
}
std::shared_ptr<Person> lookupPerson(const std::string& name) const {
return people[&name];
}
And a few edits to make it work with std::unordered_map:
struct StrPtrHash {
size_t operator()(const std::string* p) const {
return std::hash<std::string>()(*p);
}
};
struct StrPtrEquality {
bool operator()(const std::string* a, const std::string* b) const {
return std::equal_to<std::string>()(*a, *b);
}
};
std::unordered_map<const std::string*, std::shared_ptr<Person>, StrPtrHash, StrPtrEquality> people();

Template Class to differentiate object type?

Can I use C++ template classes to differentiate object types? Or what should I use?
Eg. I have a class Synonym and it can be of type Statement, Procedure, etc for example. I have functions that accepts these synonyms and evaluates them depending on its type. So I was thinking it will be nice if I can do something like:
enum Types { Statement, Procedure, Variable, ... };
template <typename Types>
class Synonym { ... }
void evaluate(Synonym<Statement> s, Synonym<Variable> v) { do something }
^ so that I can do this ... instead of checking the type in function like:
void evaluate(Synonym s, Synonym v) {
assert(s.type == Statement);
assert(v.type == Variable);
// also would like to eliminate things like: (if possible)
switch(s.type) {
case XXX: doSomething ...
case YYY: doAnotherThing ...
}
}
You could create a function template and then specialize on that template
template<typename Type>
void evaluate (Type t) {}
template<>
void evaluate<Statement>( Statement s)
{}
This way, when you pass a Statement it will pick that overload, and you can do different behaviors depending on type.
I think using a variant and visitor pattern would be suited. Have a look at Boost.Variant here: http://www.boost.org/doc/libs/1_51_0/doc/html/variant.html, the last example (also below but expanded) shows a visitor implementation. There are also other variant and visitor implementations. std::any and loki are also options. I personally like loki but that is probably just because I'm a huge fan of Alexandrescu.
#include "boost/variant.hpp"
#include <iostream>
class ToLengthVisitor : public boost::static_visitor<int>
{
public:
int operator()(int i) const
{
return i;
}
int operator()(const std::string & str) const
{
return str.length();
}
int operator()(const char * str) const
{
const char * temp = str;
while(*temp != '\0') temp++;
return temp-str;
}
};
int main()
{
typedef boost::variant< int, std::string, const char * > MyVariant;
MyVariant u(std::string("hello world"));
std::cout << u; // output: hello world
MyVariant cu(boost::get<std::string>(u).c_str());
int result = boost::apply_visitor( ToLengthVisitor(), u );
std::cout << result; // output: 11 (i.e., length of "hello world")
result = boost::apply_visitor( ToLengthVisitor(), cu );
std::cout << result; // output: 11 (i.e., length of "hello world")
}