这是对这个问题的跟进。最初的案例是另外一回事,但在我写一个糟糕的答案和 OP 澄清的过程中,结果证明我们可能需要语言律师的帮助来了解发生了什么。
在 Thinking in C++ - Practical Programming Vol 2 中,可以找到以下示例(我的意图,在此处在线):
//: C05:FriendScope3.cpp {-bor} // Microsoft: use the -Za (ANSI-compliant) option #include <iostream> using namespace std; template<class T> class Friendly { T t; public: Friendly(const T& theT) : t(theT) {} friend void f(const Friendly<T>& fo) { cout << fo.t << endl; } void g() { f(*this); } }; void h() { f(Friendly<int>(1)); } int main() { h(); Friendly<int>(2).g(); } ///:~
他们继续解释(强调我的):
这个和前面的例子有一个重要的区别:这里的 f 不是模板,而是一个普通的函数。(请记住,在暗示 f( ) 是一个模板之前,尖括号是必需的。)每次实例化 Friendly 类模板时,都会创建一个新的普通函数重载,它接受当前 Friendly 特化的参数。这就是丹萨克斯所说的结交新朋友。[68] 这是为模板定义友元函数的最方便的方法。
到现在为止还挺好。当你考虑这个例子时,令人费解的部分是“这里的 f 不是模板,而是一个普通函数”+“每次实例化 Friendly 类模板时,都会创建一个新的、普通的函数重载”:
template <typename T>
struct foo {
friend void bar(foo x){
x = "123";
}
};
int main() {
foo<int> x;
bar(x);
}
实例化foo<int>
不会导致编译器错误!仅调用bar(x)
原因(gcc 10.2):
<source>: In instantiation of 'void bar(foo<int>)':
<source>:10:10: required from here
<source>:4:11: error: no match for 'operator=' (operand types are 'foo<int>' and 'const char [4]')
4 | x = "123";
| ~~^~~~~~~
<source>:2:8: note: candidate: 'constexpr foo<int>& foo<int>::operator=(const foo<int>&)'
2 | struct foo {
| ^~~
<source>:2:8: note: no known conversion for argument 1 from 'const char [4]' to 'const foo<int>&'
<source>:2:8: note: candidate: 'constexpr foo<int>& foo<int>::operator=(foo<int>&&)'
<source>:2:8: note: no known conversion for argument 1 from 'const char [4]' to 'foo<int>&&'
普通函数的实例化?只有在调用函数时才会失败?这里发生了什么?
真的bar
是普通功能吗?它仅在调用时实例化?为什么,当它是一个普通函数时?bar
关于何时实例化实际发生了什么foo<int>
(作者称其为“创建了一个新的、普通的函数重载”,不确定这应该是什么意思)?
对不起,这太令人费解了。请不要错过这个language-lawyer
标签,我想知道为什么/标准的哪些部分使它如此,而不仅仅是什么。
PS:为了确保我再次检查,三个常见的嫌疑人在bar
没有被调用时都编译了这个例子而没有重大抱怨:https ://godbolt.org/z/Wcsbc5qjv