C++将友元添加到模板类中以便进行类型转换

C++ adding friend to a template class in order to typecast

本文关键字:类型转换 友元 添加 C++      更新时间:2023-10-16

我目前正在阅读"有效C++",其中有一章包含与此类似的代码:

template <typename T>
class Num {
public:
Num(int n) { ... }
};
template <typename T>
Num<T> operator*(const Num<T>& lhs, const Num<T>& rhs) { ... }
Num<int> n = 5 * Num<int>(10);

书中说,这是行不通的(事实上也不可行),因为你不能指望编译器使用隐式类型转换来专门化模板。

作为一种解决方案,建议使用"friend"语法来定义类中的函数。

//It works
template <typename T>
class Num {
public:
Num(int n) { ... }
friend 
Num operator*(const Num& lhs, const Num& rhs) { ... }
};
Num<int> n = 5 * Num<int>(10);

这本书建议,每当我需要隐式转换为模板类类型时,就使用这个友元声明。这一切似乎都很有道理。

但是,为什么我不能用一个通用函数而不是运算符来获得相同的例子呢?

template <typename T>
class Num {
public:
Num(int n) { ... }
friend 
void doFoo(const Num& lhs) { ... }
};
doFoo(5);

这一次,编译器抱怨他根本找不到任何"doFoo"。如果我在类外声明doFoo,我会得到合理的不匹配类型错误。似乎"朋友…"的部分被忽略了。

那么我的理解有问题吗?在这种情况下,函数和运算符之间有什么区别?

原因是这里的

doFoo(5);

编译器没有办法在给定int参数的情况下找到foo。这相当于给你的朋友打这样的电话:

Num<int> n = 5 * 10;

这将"起作用",但不是通过调用Num类中定义的friend operator*,而是通过调用整数的内置operator*,然后使用Num的转换构造函数的隐式转换。

核心问题是查找。friend声明提供了命名空间级别函数的声明,但该声明仅在与之成为朋友的类内部可用。在本书提供的示例中,这不是问题:该函数接受两个封闭类型的参数,只要其中一个是封闭类型的,参数相关查找将在类的定义内部查找运算符。在您的情况下,情况并非如此,因为只有一个参数需要转换,所以编译器不会查看类的定义内部。

请注意,这与模板和转换无关:

class A {
friend void f( int ) {}
friend void g( int, A ) {}
};
int main() {
f(5);           // Error: lookup cannot find 'f' declared *only* inside A
g(5,A());       // Ok, one argument is 'A', lookup will find the function
}

在上面的例子中,如果不涉及模板,您可能会在命名空间级别添加一个声明来修复它,但这实际上不是模板类的一个选项。

class A {
friend void f() { std::cout << "inside An"; }
};
void f(int);     // only declaration
int main() {
f(5);         // "inside A"
}

不能对模板(以及所有实例化类型)执行此操作,因为友元声明是非模板化函数的声明。尽管您可以仅仅为了测试而使用代码:

template <typename T>
struct Num {
Num(int x) ...
friend void f( Num const & );
};
Num<int> f(Num<int> const &);    // only declaration
int main() {
f(5);
}
是的,这些代码编译器不知道如何使用它。喜欢doFoo(5)

编译器不知道5是int