问题是编译器没有尝试使用模板化operator<<
您提供的,而是非模板版本。
当您在类中声明友元时,您将在封闭范围内注入该函数的声明。以下代码的作用是声明(而不是定义)一个带有non_template_test
通过常量引用进行参数:
class non_template_test
{
friend void f( non_template_test const & );
};
// declares here:
// void f( non_template_test const & );
模板类也会发生同样的情况,即使在这种情况下它不太直观。当您在模板类主体中声明(而不是定义)友元函数时,您就是在声明一个具有该确切参数的自由函数。请注意,您正在声明一个函数,而不是模板函数:
template<typename T>
class template_test
{
friend void f( template_test<T> const & t );
};
// for each instantiating type T (int, double...) declares:
// void f( template_test<int> const & );
// void f( template_test<double> const & );
int main() {
template_test<int> t1;
template_test<double> t2;
}
这些自由函数已声明但未定义。这里棘手的部分是这些自由函数不是模板,而是声明的常规自由函数。当您将模板函数添加到组合中时,您会得到:
template<typename T> class template_test {
friend void f( template_test<T> const & );
};
// when instantiated with int, implicitly declares:
// void f( template_test<int> const & );
template <typename T>
void f( template_test<T> const & x ) {} // 1
int main() {
template_test<int> t1;
f( t1 );
}
当编译器调用 main 函数时,它会实例化模板template_test
与类型int
并声明了 free 函数void f( template_test<int> const & )
那不是模板化的。当它发现呼叫时f( t1 )
那里有两个f
匹配的符号:非模板f( template_test<int> const & )
声明(且未定义)时template_test
被实例化,并且声明和定义的模板化版本1
。非模板化版本优先,编译器会匹配它。
当链接器尝试解析非模板化版本时f
它找不到该符号,因此失败。
我们可以做什么?有两种不同的解决方案。在第一种情况下,我们让编译器为每个实例化类型提供非模板化函数。在第二种情况下,我们将模板化版本声明为友元。它们略有不同,但在大多数情况下是等效的。
让编译器为我们生成非模板化函数:
template <typename T>
class test
{
friend void f( test<T> const & ) {}
};
// implicitly
这具有根据需要创建尽可能多的非模板化自由函数的效果。当编译器在模板中找到友元声明时test
它不仅找到声明,还找到实现,并将两者添加到封闭范围中。
使模板版本成为朋友
为了使模板成为友元,我们必须已经声明它并告诉编译器我们想要的友元实际上是一个模板,而不是一个非模板化的自由函数:
template <typename T> class test; // forward declare the template class
template <typename T> void f( test<T> const& ); // forward declare the template
template <typename T>
class test {
friend void f<>( test<T> const& ); // declare f<T>( test<T> const &) a friend
};
template <typename T>
void f( test<T> const & ) {}
在这种情况下,在声明之前f
作为模板,我们必须转发声明模板。声明f
模板我们必须首先声明test
模板。好友声明被修改为包含尖括号,用于标识我们要创建好友的元素实际上是一个模板而不是一个自由函数。
回到问题
回到您的特定示例,最简单的解决方案是让编译器通过内联友元函数的声明来为您生成函数:
template <typename T>
class BinaryTree {
friend std::ostream& operator<<( std::ostream& o, BinaryTree const & t ) {
t.dump(o);
return o;
}
void dump( std::ostream& o ) const;
};
使用该代码,您将强制编译器生成非模板化的operator<<
对于每个实例化类型,以及生成的函数委托dump
模板的方法。