I'm a move
semantics beginner. Is this code:
template <typename... Args>
void foo(const Args & ... args){
map<tuple<Args...>, int> cache;
auto result = cache.emplace(move(make_tuple(args ...)),1);
//...
}
More efficient than:
template <typename... Args>
void foo(const Args & ... args){
map<tuple<Args...>, int> cache;
tuple<Args...> t(args...);
auto result = cache.insert(make_pair(t,1));
//...
}
Especially if args
contains some big object?
Same question but with std::vector
(so no need for make_pair
or make_tuple
)
Since this is for memoization, neither option is a good idea.
For unique-key containers, emplace
and insert
(except when insert
is passed a value_type
- that is, pair<const Key, Value>
) may unconditionally allocate memory and construct the key-value pair first, and then destroy the pair and deallocate the memory if the key already exists; this is obviously costly if your key already exists. (They need to do this because in the general case you have to construct the key before you can check if it exists, and the key must be constructed directly at its final location.)
However, you also want to avoid unnecessarily copying the key, so inserting a value_type
is no good - the Key
in there is const-qualified and so can't be moved from.
Finally, you'd also want to avoid extra lookups. Not as costly as a memory allocation, but still good to save it.
Thus, we need to lookup the key first, and only call emplace
if the key is not in the map. In C++11, only homogeneous lookup is allowed, so you have to make one copy of args...
.
map<tuple<Args...>, int> cache;
auto key = std::make_tuple(args...);
auto it = cache.lower_bound(key); // *it is the first element whose key is
// not less than 'key'
if(it != cache.end() && it->first == key) {
// key already in the map, do what you have to do
}
else {
// add new entry, using 'it' as hint and moving 'key' into container.
cache.emplace_hint(it, std::move(key), /* value */);
}
In C++14, you can do heterogeneous lookup, which means that you can save the copy for when you actually need it:
map<tuple<Args...>, int, less<>> cache; // "diamond functor" enables hetergeneous lookup
auto key = std::tie(args...); // this is a tuple<const Args&...> - a tuple of references!
auto it = cache.lower_bound(key); // *it is the first element whose key is
// not less than 'key'
if(it != cache.end() && it->first == key) {
// key already in the map, do what you have to do
}
else {
// add new entry, copying args...
cache.emplace_hint(it, key, /* value */);
}