Is this the right way to implement a simple hash table in C++? Does this look like a proper implementation of a hash table in C++? Any errors anyone can find?
Thanks in advance (:

template &lt;typename T&gt;
class HashTable
{
public:
HashTable(int amount)
{
    size = amount;
    values = new T*[size];
    for (int i = 0; i &lt; size; i++)
    {
        *(values + i) = nullptr;
    }
}
~HashTable()
{
    if(values != nullptr)
    {
        for (int i = 0; i &lt; size; i++)
        {
            if(*(values + i) != nullptr)
            {
                delete *(values + i);
                *(values + i) = nullptr;
            }
        }
        delete[] values;
    }
}

void addValue(const std::string &amp; key, T value)
{
    int location = hash(key);

    if (*(values + location) == nullptr)
    {
        *(values + location) = new T;
    }

    **(values + location) = value;
}

void printValues()
{
    for (int i = 0; i &lt; size; i++)
    {
        if (*(values + i) != nullptr)
        {
            printf("value : %i\n", **(values + i));
        }
    }
}

T* getValue(const std::string &amp; key)
{
    return *(values + hash(key))
}

private:

int hash(const std::string &amp; key)
{
    int value = 0;
    for (unsigned int i = 0; i &lt; key.length(); i++)
    {
        value += key[i] * (i + 1);
    }
    return value % size;
}

int size;
T ** values = nullptr;
};
The biggest issue is, as mentioned in the comments, that you don't handle collisions. I'm not going to cover that, but I'll address some other issues.

IMO your hashtable should be templated on at least 3 types - the key type, the value type, and the hash functor.  This way you aren't limiting your users to having string keys or to rely on your hash function.

You should also use colon initialization - the data members will be default initialized at the starting curly brace of the constructor, but with colon initialization you skip the default initialization and the replacement with new values.  That would look something like this

HashTable(size_t amount) : size(amount), values(new Value*[size]) { 
    for (size_t i = 0; i &lt; size; ++i) {
        values[i] = nullptr; 
    }   
}


You'll notice I've replaced your ints with size_t - in these situations you don't need negative numbers.  I've also used the subscript notation instead of pointer arithmatic, which is much, much easier to read and reason about. You should strongly prefer subscript notation if it is possible to use it.

Your destructor doesn't need to check if the the values are not nullptr - there isn't a sensible reason it should be.

You should be using const functions - printValues should be, and you should provide a const version of getValue as well.

You also assume that the value type can be default initialized, which is not necessarily true. You might want to use placement new here instead. Alternatively, you could just split up the code a bit.

void addValue(const std::string &amp; key, T value)
{
    int location = hash(key);

    if (values[location] == nullptr) 
    {
        values[location] = new T(value);
    }
    else
    {
        *values[location] = value;
    }
}


This still requires that there be a copy constructor, however this is probably a less surprising requirement (most containers expect objects to be copy/move constructible).

You'll probably want to provide an iterator as well, but I'll leave that as an exercise for the reader.

Lastly, you should provide a method to find and erase, and probably resize your hash table.  Without those things the hash table doesn't have much use.