【发布时间】:2021-07-22 05:48:53
【问题描述】:
这是this question 的后续行动。最初的案例是另外一回事,但在我写一个糟糕的答案和 OP 澄清的过程中,结果证明我们可能需要语言律师的帮助才能理解发生了什么。
在 Thinking in C++ - Practical Programming Vol 2 中可以找到以下示例(我的意图,在线 here):
//: 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 类模板时,都会创建一个新的普通函数重载,它接受当前的参数友好的专业化。这就是丹萨克斯所说的结交新朋友。 [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真的是普通函数吗?它仅在调用时实例化?为什么,当它是一个普通函数时?当 foo<int> 被实例化时,bar 实际发生了什么(作者称其为“创建了一个新的、普通的函数重载”,不知道这应该是什么意思)?
对不起,这太令人费解了。请不要错过language-lawyer 标签,我想知道为什么/标准的哪些部分会这样,而不仅仅是什么。
PS:为了确保我再次检查,三个常见的嫌疑人在没有调用bar 时都编译了这个示例而没有大的抱怨:https://godbolt.org/z/Wcsbc5qjv
【问题讨论】:
-
AFAIK,在类模板中声明的函数的定义(朋友也不例外)只有在使用 ODR 时才真正实例化,否则只有声明被实例化。这就是为什么只要不使用
operator[]就可以用非默认可构造类型实例化std::map。 -
@Meowmere 听起来很合理,尽管这样编译也应该没有错误(不同的是现在
bar不依赖于T)godbolt.org/z/b8nq5abWq,但现在clang 已经在实例化时出错了的foo。那么 gcc 和 msvc 同时接受它,也许这将是下一次跟进。到目前为止,我虽然了解map::operator[]的情况,它是类模板的成员,但这里bar不是成员。 -
@largest 嗯。有关于模板制作程序 if;ndr 没有有效的专业化(标准意味着实例化)的规则。但是如果它不是一个模板... Then0 又是一个模板的方法。无论如何,标准中有十几个 if;ndr,看看那个,它可能适用。
-
这听起来像是概念如何提供帮助的一个很好的例子:godbolt.org/z/qK1sxq4Ts
标签: c++ templates language-lawyer friend template-instantiation