C++ Implementation of a dictionary data structure I am quite new to C++ and would like some feedback on my implementation of a dictionary data structure. The code is below.

#ifndef DICTIONARY_H_INCLUDED
#define DICTIONARY_H_INCLUDED

#include &lt;initializer_list&gt;
#include &lt;algorithm&gt;
#include &lt;vector&gt;

template &lt;typename T, typename U&gt;
class Dictionary{
    private:
        std::vector&lt;T&gt; keys;
        std::vector&lt;U&gt; values;
    public:
        Dictionary();
        Dictionary(std::initializer_list&lt;std::pair&lt;T,U&gt;&gt;);
        bool has(T) const;
        void add(T,U);
        T* begin();
        T* end();
        U operator[](T);
};

template &lt;typename T, typename U&gt;
T* Dictionary&lt;T,U&gt;::begin(){
    return &amp;(keys[0]);
}

template &lt;typename T, typename U&gt;
T* Dictionary&lt;T,U&gt;::end(){
    return &amp;(keys[keys.size()-1])+1;
}

template &lt;typename T, typename U&gt;
Dictionary&lt;T,U&gt;::Dictionary (std::initializer_list&lt;std::pair&lt;T,U&gt;&gt; store){
    for (std::pair&lt;T,U&gt; object : store){
        keys.push_back(object.first);
        values.push_back(object.second);
    }
}

template &lt;typename T, typename U&gt;
bool Dictionary&lt;T,U&gt;::has(T targetKey) const{
    for (T currentKey : keys){
        if (currentKey == targetKey){
            return true;
        }
    }
    return false;
}

template &lt;typename T, typename U&gt;
void Dictionary&lt;T,U&gt;::add (T key, U value){
    keys.push_back(key);
    values.push_back(value);
}

template &lt;typename T, typename U&gt;
U Dictionary&lt;T,U&gt;::operator[] (T key){
    unsigned int pos = std::find(keys.begin(), keys.end(), key) - keys.begin();
    return values[pos];
}

#endif // DICTIONARY_H_INCLUDED
The representation (as a parallel pair of vectors) isn't as good as the alternative (a single vector of pairs), because:


any time the vectors have to grow, there's two separate allocations rather than just one, and
whenever they are used, the data may be in different regions of memory (the code has poorer spatial locality).


Consider instead writing

using value_type = std::pair&lt;T, U&gt;;
std::vector&lt;value_type&gt; entries;


This will change the interface a little - in particular, begin() and end() will now give iterators that access these pairs.  That's more consistent with std::map interface, so I recommend that.



We can reduce duplication a little: in the initializer-list constructor, we can simply call add() for each element rather than re-writing its body:

template &lt;typename T, typename U&gt;
Dictionary&lt;T,U&gt;::Dictionary (std::initializer_list&lt;std::pair&lt;T,U&gt;&gt; store)
{
    for (std::pair&lt;T,U&gt; object : store) {
        add(object.first, object.second);
    }
}


On the other hand, if we've changed our representation as above, we simply have

template &lt;typename T, typename U&gt;
Dictionary&lt;T,U&gt;::Dictionary (std::initializer_list&lt;std::pair&lt;T,U&gt;&gt; store)
{
    for (std::pair&lt;T,U&gt; object : store) {
        entries.push_back(object);
    }
}




The indexing operator is surprising.  If key doesn't exist in the dictionary, then we have undefined behaviour (accessing values out of range).  That's a valid choice, but worthy of a comment (particularly as it diverges from the Standard Library interface).  Since it doesn't create as necessary, and only ever returns a copy of the value (rather than a reference) it probably ought to be declared const.



We should probably check whether key already exists in add().  We can then either ignore it, or update the existing value.  What we have at the moment is the worst possible choice - we add data that can never be used, which only serves to slow future operations.



Some other useful accessors are missing - constant and/or reverse iterators spring to mind as an obvious example, as do methods of removing values.  A swap() member would also be very useful.



The has function can be simplified a bit with the use of a standard algorithm: std::any_of() can replace the loop.



Algorithmic complexity

Our operator[] uses linear search with std::find().  Whilst this works, it will get slower in proportion to the number of elements contained - in "big O" notation, we say that it scales as O(n).  Once we have the interface as we we want, we'll then want to look at changing the data structure to improve look-up times (and we might need to trade against insertion speed).  The good news is that storing items as pairs will work well with whatever storage we choose, and it will be much easier to change the implementation than with separate storage of keys and values.

It may well be worth moving to pair-based storage, and implementing the other review items, and then returning for a performance review of the new code.