C++ - Graph implementation for data types other than int I am learning graphs and most of the implementations that I see are meant for int. So, I decided to code an example that can take other data types such as char or float. I did see this prior to posting my example (but not prior to coding my example). The example in the link is similar to creating a Tree where a separate Node class is used to keep track of node value, left and right child. In the link, instead of left and right child, the GraphNode class keeps track of neighbors.

My code does not use that approach and instead keeps the Adjacency List completely to Graph class, similar to usual implementations meant for int type. The idea is that if anyone needs to define a custom data type, it can be coded later (So, my code does not have a counterpart for GraphNode yet). The Graph is initialized by a vector of edges. The edges are represented by std::pair of nodes/ vertices involved. I have posted my code below. I am interested in knowing if there is a way to represent adjacency list, better than a map of lists, using stl containers, and can still handle different data types. However, any reviews or improvements or suggestions are welcome:

#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;list&gt;
#include &lt;map&gt;

template&lt;typename T&gt;
void print_list(std::list&lt;T&gt; l)
{
    typename std::list&lt;T&gt;::const_iterator it;
    for(it = l.cbegin(); it != l.cend(); it++)
    {
        std::cout &lt;&lt; *it &lt;&lt; "\t";
    }
    std::cout &lt;&lt; std::endl;
}

template&lt;typename T&gt;
class Graph
{
    //Undirected Graph
    std::map&lt;T, std::list&lt;T&gt;&gt; adj_list;
public:
    Graph(std::vector&lt;std::pair&lt;T, T&gt;&gt;);
    void add_edge(std::pair&lt;T, T&gt;);
    size_t size();
    void print_adj_list();
};

template&lt;typename T&gt;
Graph&lt;T&gt;::Graph(std::vector&lt;std::pair&lt;T, T&gt;&gt; edges)
{
    for(size_t i = 0; i &lt; edges.size(); i++)
    {
        this -&gt; add_edge(edges[i]);
    }
}

//Undirected Graph
template&lt;typename T&gt;
void Graph&lt;T&gt;::add_edge(std::pair&lt;T, T&gt; edge)
{
        adj_list[edge.first].push_back(edge.second);
        adj_list[edge.second].push_back(edge.first);
}

template&lt;typename T&gt;
size_t Graph&lt;T&gt;::size()
{
    std::cout &lt;&lt; "Size of Graph(no. of vertices): " &lt;&lt; std::endl;
    return adj_list.size();
}

template&lt;typename T&gt;
void Graph&lt;T&gt;::print_adj_list()
{
    std::cout &lt;&lt; "The Adjacency List: " &lt;&lt; std::endl;
    typename std::map&lt;T, std::list&lt;T&gt;&gt;::const_iterator it;
    for(it = adj_list.cbegin(); it != adj_list.cend(); it++)
    {
        std::cout &lt;&lt; it -&gt; first &lt;&lt; "\t";
        print_list&lt;T&gt;(it -&gt; second);
    }

}
int main()
{
    /*std::vector&lt;std::pair&lt;int, int&gt;&gt; v;
    v.push_back({1, 2});
    v.push_back({2, 8});
    v.push_back({2, 5});
    v.push_back({2, 4});
    v.push_back({3, 4});
    v.push_back({5, 9});
    v.push_back({5, 7});
    v.push_back({5, 6});
    Graph&lt;int&gt; g{v};*/

    std::vector&lt;std::pair&lt;char, char&gt;&gt; v;
    v.push_back({'a', 'b'});
    v.push_back({'b', 'h'});
    v.push_back({'b', 'e'});
    v.push_back({'b', 'd'});
    v.push_back({'c', 'd'});
    v.push_back({'e', 'i'});
    v.push_back({'e', 'g'});
    v.push_back({'e', 'f'});
    Graph&lt;char&gt; g{v};
    g.print_adj_list();
    std::cout &lt;&lt; g.size() &lt;&lt; std::endl;
    g.add_edge({'i', 'k'});
    g.add_edge({'f', 'k'});
    g.print_adj_list();
    std::cout &lt;&lt; g.size() &lt;&lt; std::endl;
    return 0;
}
Pass parameters by const &amp;

In print_list and your Graph constructor, you pass in the parameters by value.  This will make unnecessary copies of the vectors.  Passing in the values by const reference will avoid making these copies.

Make use of the auto keyword or range-based for loops

You have several places with a long type name to get the type to use for an iterator.  These would be great spots to make use of the auto keyword.  For example, auto it = l.cbegin().

However, in these cases, you can do better.  You can use the range-based for statement instead.

Combining these for the print_list function gives us

template&lt;typename T&gt;
void print_list(const std::list&lt;T&gt; &amp;l)
{
    for (auto v: l)
        std::cout &lt;&lt; v &lt;&lt; '\t';
    std::cout &lt;&lt; std::endl;
}


Similar changes can be made to your Graph constructor and print_adj_list.

You can also pass the pair to add_edge by const reference, although with the small types you're using that won't really gain much.

add_edge assumes that all your edges are bidirectional.  Some adjacency lists have edges that only go in one direction.

Do you need that cout in size?  All it is really doing is telling you that the function has been called.

You should be able to call print_list without having to specify the type.  The compiler should be able to deduce the template type to use.