重载函数在C ++ 11中处理多个值类?

时间:2015-01-24 19:03:11

标签: c++ c++11

我正在编写一个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种组合,这样做的正确方法是什么?

2 个答案:

答案 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>。您无需明确指定它。