Search code examples
c++11undefined-reference

friend declaration declares a non-template function, undefined reference


I have such template class, which includes a friend method:

template<class T1, class T2, int n>
class Graph final
{
private:
    std::array<T1, n> vertex;
    std::array<std::array<T2, n>, n> arcs;
public:
    Graph(const std::array<T1, n> & v, const std::array<std::array<T2, n>, n> & a);
    friend std::ostream & operator<<(std::ostream & os, const Graph<T1, T2, n> & g);
};
template<class T1, class T2, int n>
Graph<T1, T2, n>::Graph(const std::array<T1, n> & v, const std::array<std::array<T2, n>, n> & a)
    : vertex(v)
{ ... }

template<class T1, class T2, int n>
std::ostream & operator<<(std::ostream & os, const Graph<T1, T2, n> & g)
{ ... }

This class is used to save graphs. Vertex is an array that stores nodes, and T1 is the corresponding element type. Arcs is a two-dimensional array of n * n that stores the path overhead between nodes, and T2 is the corresponding element type.(Note that n is the template non-type parameters.)

And I try to create a Graph and call friend method like this:

void test() {
    using std::array;
    array<int, 5> vertex = {100, 200, 300, 400, 500};

    array<int, 5> a = {2, 3, 2, 4, 5};
    array<array<int, 5>, 5> arcs = {a, a, a, a, a};

    Graph<int, int, 5> g(vertex, arcs);
    std::cout << g << std::endl;
}

Only get Error:

tree_mst.h:31:87: warning: friend declaration ‘std::ostream& meyok::operator<<(std::ostream&, const Graph<T1, T2, n>&)’ declares a non-template function [-Wnon-template-friend]
         friend std::ostream & operator<<(std::ostream & os, const Graph<T1, T2, n> & g);
                                                                                       ^
tree_mst.h:31:87: note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here)

../lib/libmtree.so: undefined reference to `operator<<(std::ostream&, Graph<int, int, 5ull> const&)'
../lib/libmtree.so: undefined reference to `Graph<int, int, 5ull>::Graph(std::array<int, 5ul> const&, std::array<std::array<int, 5ul>, 5ul> const&)'

Why are warnings and errors popping up here. How can I solve it?


Solution

  • The friend declaration declares, for each instantiation of Graph, a regular non-template function named operator<< taking this instantiation as a parameter. For example, your program uses Graph<int, int, 5> - when the compiler first sees that, it produces a declaration

    std::ostream & operator<<(std::ostream & os, const Graph<int, int, 5>& g);
    

    Note that this is separate and distinct from your operator<< function template.

    Then the compiler sees std::cout << g and performs an overload resolution to select the best overload for operator<<. Since a non-template function is preferred over the function template, other things equal, the function introduced by friend declaration is selected. Finally, the linker discovers that this function is never actually implemented; hence the error.

    Most likely, you meant to say a specialization of operator<< function template that matches the Graph template parameters should be the friend of that Graph specialization. You express it this way:

    // Forward declaration
    template<class T1, class T2, int n>
    class Graph;
    
    // Forward declaration
    template<class T1, class T2, int n>
    std::ostream & operator<<(std::ostream & os, const Graph<T1, T2, n> & g);
    
    template<class T1, class T2, int n>
    class Graph
    {
    public:
        friend std::ostream & operator<< <T1, T2, n>(std::ostream & os, const Graph<T1, T2, n> & g);
    };
    

    However, it's usually simpler to avoid friend altogether, and have operator<< delegate to a member function:

    template<class T1, class T2, int n>
    class Graph
    {
    public:
        void print(std::ostream& os) const { /* actual output logic here */);
    };
    
    template<class T1, class T2, int n>
    std::ostream& operator<<(std::ostream& os, const Graph<T1, T2, n>& g) {
      g.print(os);
      return os;
    }