在模板中定义友元函数的实例化
Instantiation of friend function defined inside a template
这是 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();
} ///:~
他们继续解释(强调我的):
There is an important difference between this and the previous example: f is not a template here, but is an ordinary function. (Remember that angle brackets were necessary before to imply that f( ) was a template.) Every time the Friendly class template is instantiated, a new, ordinary function overload is created that takes an argument of the current Friendly specialization. This is what Dan Saks has called making new friends. [68] This is the most convenient way to define friend functions for templates.
到目前为止一切顺利。令人费解的部分是“f在这里不是模板,而是一个普通函数”+“每次实例化 Friendly class 模板时,都会创建一个新的普通函数重载”,当你考虑这个例子时:
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
[temp.inst]/2 The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions, default arguments, or noexcept-specifiers of the class ... friends...
[temp.inst]/4 ... A function whose declaration was instantiated from a friend function definition is implicitly instantiated when it is referenced in a context that requires a function definition to exist...
像这样的构造是模板的一部分但本身不是模板,因此称为 templated 因为它们仍然遵守许多相同的规则(尤其是在方法和朋友的地方) class 模板分别实例化,赋予每个模板自己的“实例化状态”)。标准本身一直在慢慢使用更精确的语言来应对这种情况,部分原因是 constexpr-if 引入了模板化的 statements(因为它们必须单独实例化,以允许这样做只有一个分支),即使没有语句模板。 (可能对进一步研究有用的这些结构的旧术语是“temploids”。)
这是 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(); } ///:~
他们继续解释(强调我的):
There is an important difference between this and the previous example: f is not a template here, but is an ordinary function. (Remember that angle brackets were necessary before to imply that f( ) was a template.) Every time the Friendly class template is instantiated, a new, ordinary function overload is created that takes an argument of the current Friendly specialization. This is what Dan Saks has called making new friends. [68] This is the most convenient way to define friend functions for templates.
到目前为止一切顺利。令人费解的部分是“f在这里不是模板,而是一个普通函数”+“每次实例化 Friendly class 模板时,都会创建一个新的普通函数重载”,当你考虑这个例子时:
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
[temp.inst]/2 The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions, default arguments, or noexcept-specifiers of the class ... friends...
[temp.inst]/4 ... A function whose declaration was instantiated from a friend function definition is implicitly instantiated when it is referenced in a context that requires a function definition to exist...
像这样的构造是模板的一部分但本身不是模板,因此称为 templated 因为它们仍然遵守许多相同的规则(尤其是在方法和朋友的地方) class 模板分别实例化,赋予每个模板自己的“实例化状态”)。标准本身一直在慢慢使用更精确的语言来应对这种情况,部分原因是 constexpr-if 引入了模板化的 statements(因为它们必须单独实例化,以允许这样做只有一个分支),即使没有语句模板。 (可能对进一步研究有用的这些结构的旧术语是“temploids”。)