我正在编写一个C ++ 11 hashmap类(主要是为了好玩),我希望我的insert函数能够有效地处理任何值类(即在可能的情况下它使用移动语义)。
所以我所做的就是编写插入的4个重载:
template <class K, class V>
struct hashmap {
<snip>
hashmap<K,V>& insert (K&& key, V&& val);
hashmap<K,V>& insert (K key, V&& val);
hashmap<K,V>& insert (K&& key, V val);
hashmap<K,V>& insert (K key, V val);
<snip>
};
并定义它们,因此具有两个右值引用的那个是“真正的”插入,其他人使用std :: forward / std :: move来适当地将它们的参数传递给它。
// insert operator. If the key doesn't exist in the table, build it with
// the given value. If it does exist, overwrite it with new value. Other
// insert variants wrap this with appropriate forward/move semantics
template <class K, class V>
hashmap<K,V>& hashmap<K,V>::insert(K&& key, V&& val) {
using std::swap; // for ADL
using std::move;
using std::forward;
if (capacity_ == 0) {
resize_(DEFAULT_SIZE);
}
size_t hash = hash_key(key);
size_t idx = modulo(hash, capacity_);
// scan through container until we find an empty bucket. If we find the key
// along the way, just set its value and we're done. If we find any elements
// with a smaller probe distance than ours, swap with them and continue loop
bool swapped=false;
for (size_t ii=0, curdist=0; ii < capacity_; ii++, curdist++) {
size_t pos = modulo(idx + ii, capacity_);
if (storage_[pos].hash == EMPTY_BUCKET) {
// found an empty bucket, place what we currently have there
if (used_ >= 3*capacity_/4) {
// if loading is > 75% then resize the table and call back to insert to place element
resize_(2*capacity_);
insert(forward<K>(key), forward<V>(val));
} else {
// otherwise just build the new hash bucket as requested
storage_[pos].hash = hash;
new (&storage_[pos].kv.key) K(forward<K>(key));
new (&storage_[pos].kv.val) V(forward<V>(val));
used_++;
}
break;
} else if (!swapped && storage_[pos].hash == hash && (storage_[pos].kv.key == key)) {
// found a bucket that matches our key, update it
storage_[pos].kv.val = forward<V>(val);
break;
} else {
// found non-empty bucket that doesn't match us. If it has a probe distance
// less than ours, we'll swap with it and continue looking to place the element
size_t newdist = probedist(storage_[pos].hash, pos);
if (newdist < curdist) {
curdist = newdist;
swap(storage_[pos].hash, hash);
swap(storage_[pos].kv.key, key);
swap(storage_[pos].kv.val, val);
swapped = true;
}
}
}
return *this;
}
// insert variants for different value classes
template <class K, class V>
hashmap<K,V>& hashmap<K,V>::insert(K key, V&& val) { insert(std::move(key), std::forward(val)); return *this; }
template <class K, class V>
hashmap<K,V>& hashmap<K,V>::insert(K&& key, V val) { insert(std::forward(key), std::move(val)); return *this; }
template <class K, class V>
hashmap<K,V>& hashmap<K,V>::insert(K key, V val) { insert(std::move(key), std::move(val)); return *this; }
当然,在我做了这个更改后,我尝试将一个std :: string,size_t对插入到一个实例中:
maph.insert(*ptr++, ii);
并且立即收到编译错误,抱怨模糊的调用:
src/check_hash.cc:313:5: required from here
src/check_hash.cc:128:13: error: call of overloaded ‘insert(std::basic_string<char>&, size_t&)’ is ambiguous
maph.insert(*ptr++, ii);
^
src/check_hash.cc:128:13: note: candidates are:
In file included from src/check_hash.cc:9:
hashmap.h:243:23: note: prelude::{anonymous}::hashmap<K, V>& prelude::{anonymous}::hashmap<K, V>::insert(K, V&&) [with K = std::basic_string<char>; V = int]
hashmap<K,V>& hashmap<K,V>::insert(K key, V&& val) { insert(std::move(key), std::forward(val)); return *this; }
^
prelude/hashmap.h:39:27: note: prelude::{anonymous}::hashmap<K, V>& prelude::{anonymous}::hashmap<K, V>::insert(K, V) [with K = std::basic_string<char>; V = int]
hashmap<K,V>& insert (K key, V val);
为了能够容纳键/值类型的所有4种组合,这样做的正确方法是什么?
答案 0 :(得分:0)
显然根据C ++ 11规范,如果你定义一个带右值引用的函数:
void example(Type&& t);
但两者都没有:
void example(Type& t)
或
void example(const Type& t)
然后函数只能用rvalues调用,这有点意义,因为按值获取参数,临时值可以从rvalue或lvalue构造,因此它是不明确的。解决方案是采用const lvalue引用:
template <class K, class V>
struct hashmap {
<snip>
hashmap<K,V>& insert ( K&& key, V&& val);
hashmap<K,V>& insert (const K& key, V&& val);
hashmap<K,V>& insert ( K&& key, const V& val);
hashmap<K,V>& insert (const K& key, const V& val);
<snip>
};
然后手动制作副本:
// insert variants for different value classes
template <class K, class V>
hashmap<K,V>& hashmap<K,V>::insert(const K& key, V&& val) { insert(std::move(K(key)), std::forward<V>(val)); return *this; }
template <class K, class V>
hashmap<K,V>& hashmap<K,V>::insert( K&& key, const V& val) { insert(std::forward<K>(key), std::move(V(val))); return *this; }
template <class K, class V>
hashmap<K,V>& hashmap<K,V>::insert(const K& key, const V& val) { insert(std::move(K(key)), std::move(V(val))); return *this; }
答案 1 :(得分:0)
由于您计划复制/移动val
,因此请按值接受该参数。对于密钥,请使用完美的转发习惯用法。但是,您需要让编译器推导出rvalue / lvalue-ness,因为它可能与类模板参数K
不同,因为它引入了insert
的模板参数。这是一个捕获所有内容的签名:
template <typename Key, typename = std::enable_if_t<std::is_same<K, std::decay_t<Key>>>>
hashmap& insert(Key&&, V);
请注意,类定义中的hashmap
表示hashmap<K,V>
。您无需明确指定它。