友元声明声明一个非模板函数,未定义的引用

friend declaration declares a non-template function, undefined reference

提问人:MeYokYang 提问时间:11/9/2023 最后编辑:MeYokYang 更新时间:11/12/2023 访问量:34

问:

我有这样的模板类,其中包括一个朋友方法:

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)
{ ... }

此类用于保存图形。Vertex 是存储节点的数组,T1 是对应的元素类型。Arcs 是一个 n * n 的二维数组,用于存储节点之间的路径开销,T2 是对应的元素类型。(请注意,n 是模板非类型参数。

我尝试创建一个像这样调用朋友的方法:Graph

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;
}

仅得到错误:

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&)'

为什么这里会弹出警告和错误。我该如何解决?

C++11 未定义引用

评论

1赞 273K 11/9/2023
这回答了你的问题吗?为什么模板只能在头文件中实现?
0赞 MeYokYang 11/10/2023
不。实际上,这三个文件如下所示:Graph 类位于 tree_mst.h 中(包含 #ifndef、#define 语句),其定义位于 tree_mst.cpp 中(包含 #include“tree_mst.h”语句)(两者都打包到动态库中)。测试方法的文件具有 #include“tree_mst.h”语句。
0赞 273K 11/10/2023
骗子回答了你的问题。摆脱tree_mst.cpp

答:

0赞 Igor Tandetnik 11/12/2023 #1

该声明声明,对于 的每个实例化,都会声明一个常规的非模板函数,该函数以此实例化为参数。例如,您的程序使用 - 当编译器第一次看到它时,它会生成一个声明friendGraphoperator<<Graph<int, int, 5>

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

请注意,这是独立且不同于函数模板的。operator<<

然后,编译器查看并执行重载解析,以选择最佳重载。由于非模板函数优先于函数模板,因此在其他条件相同的情况下,将选择声明引入的函数。最后,链接器发现此函数从未实际实现;因此出现错误。std::cout << goperator<<friend

最有可能的是,您的意思是说与模板参数匹配的函数模板专用化应该是该专用化的朋友。你是这样表达的:operator<<GraphGraph

// 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);
};

但是,完全避免通常更简单,并将委托给成员函数:friendoperator<<

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;
}