c++perfect-forwardinglist-initialization

perfect forwarding with brace enclosed initialiser list


For personal education I am coding up a basic implementation of a hash table (although the below is probably relevant to any container that is holding types that can be list initialised) and want to use modern elements of c++ as best as I understand them - in particular in this case, perfect forwarding.

In doing so I have found I can't figure out a particular case - writing an insert function that achieves perfect forwarding while taking a brace enclosed initialiser list. Failing to write this prevents using perfect forwarding when trying to emulate the std::unordered_map like insertion style container.insert({key,value}).

To be specific, I have some hash table class which stores keys and values as std::pair<T,U> objects in (for example) lists at buckets stored in a vector (for a hash+chaining approach)

template<typename T,typename U, typename func = std::hash<T> >
class HashTable{
    std::vector<std::list<std::pair<T,U> > > data;
...
public: 
    template<typename V>
    bool insert(V &&entry)
    {
        //insert into the table by forwarding entry as
        ... = std::forward<V>(entry);
        return true; //plus some logic for failed insertion if key already exists
    }
};

I then test my container by inserting with both lvalues and rvalues through calls such as

std::pair<int32_t,std::string> example = {12,"bike"};
myHashTableObject.insert(example);                                  //example 1
myHashTableObject.insert(std::pair<int32_t,std::string>(15,"car")); //example 2
myHashTableObject.insert({28,"bus"});                               //example 3

Examples 1 and 2 compile and work fine, but example 3 does not with compiler error (for example using the default type for the third template argument func - but the error is analgous for user provided hashes)

error: no matching function for call to ‘HashTable<int, std::__cxx11::basic_string<char> >::insert(<brace-enclosed initializer list>)’

which seems reasonable - it can't figure out how to interpret what type of object the initialisation list is supposed to construct.

An easy "solution" is to forget perfect forwarding and just overload the insert function to accept a rvalue reference and a const lvalue reference to a std::pair<T,U>. But it feels like there should be a better way.

Is there an obvious way to inform the compiler that, despite being a template variable V in order to achieve perfect forwarding, it should be some kind of reference to a std::pair<T,U> argument?

How do common implementations of containers in STL achieve this? Or do they just overload?


Solution

  • {..} has not type, and so cannot be deduced in insert.

    You might work around that by providing default template type:

    template<typename V = std::pair<T, U>> // default to handle {..}
    bool insert(V&& entry)
    {
        ... = std::forward<V>(entry);
        // ..
        return true;
    }
    

    Demo