I am learning how to write generic code in C++ and I decided to write a hash table. Its key needs to be a primitive type and value can be any type. The hash function needs to be provided by the user. Type of the function is enforced to the user.
I am looking for tips on efficiency of the code and whether forwarding of the arguments are working as intended (perfect forwarding). Also in the get method, would it be better to return value type pointer instead of std::optional?
It only contains emplace back and get methods keep things tidy.
#pragma once
#include <list>
#include <optional>
#include <type_traits>
template< class KeyType > using hashFunction_t = size_t(const KeyType& key);
template < class KeyType, class ValueType, size_t nHashGroups, hashFunction_t<KeyType>* hashFunction>
class BasicHash
{
public:
template< class... ValueArgTypes >
void EmplaceBack(KeyType&& key, ValueArgTypes&&... args)
{
size_t hash = Hash(std::forward<KeyType>(key));
for (auto& pair : hashGroups[hash])
if (pair.key == key)
return;
hashGroups[hash].emplace_back(
std::forward<KeyType>(key),
std::forward<ValueType>(ValueType{ args... })
);
}
decltype(auto) Get(KeyType&& key)
{
size_t hash = Hash(std::forward<KeyType>(key));
for (auto& pair : hashGroups[hash])
if (pair.key == key)
return std::optional<ValueType>{ pair.value };
return std::optional<ValueType>{};
}
private:
size_t Hash(KeyType&& key)
{
return (*hashFunction)(static_cast<const KeyType&>(key)) % nHashGroups;
}
struct KeyValuePair
{
KeyValuePair(KeyType&& key, ValueType&& value) :
key(key), value(value)
{
}
KeyType key;
ValueType value;
};
std::list<KeyValuePair> hashGroups[nHashGroups];
};
Minimal usage of the hash table:
#include "BasicHash.h"
#include <string>
#include <iostream>
//djb2
size_t hashFunction(const std::string& key)
{
unsigned long hash = 5381;
int c;
const char* str = key.c_str();
while (c = *str++)
hash = ((hash << 5) + hash) + c;
return hash;
}
class TwoNumberStore
{
public:
TwoNumberStore(int a, int b) :
n1{ a }, n2{ b }
{
}
int n1 = 0;
int n2 = 0;
};
int main()
{
BasicHash<std::string, TwoNumberStore, 50, hashFunction> table;
table.EmplaceBack("First Two", 1, 2);
auto firstTwo = table.Get("First Two");
if (firstTwo.has_value())
{
std::cout << firstTwo.value().n1 << '\n';
std::cout << firstTwo.value().n2;
}
}
I just realised I was copying value in std::optional. Changed get to return a optional reference_wrapper instead.
decltype(auto) Get(KeyType&& key)
{
size_t hash = Hash(std::forward<KeyType>(key));
for (auto& pair : hashGroups[hash])
if (pair.key == key)
return std::optional<std::reference_wrapper<ValueType>>{ pair.value };
return std::optional<std::reference_wrapper<ValueType>>{};
}
This makes retrieving data even messier.
if (firstTwo.has_value())
{
std::cout << firstTwo.value().get().n1 << '\n';
std::cout << firstTwo.value().get().n2;
}
size_t
come from? Is it some alias forstd::size_t
? \$\endgroup\$