【问题标题】:Instantiation of friend function defined inside a template模板内定义的友元函数的实例化
【发布时间】: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&lt;int&gt; 不会导致编译器错误!仅调用 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&lt;int&gt; 被实例化时,bar 实际发生了什么(作者称其为“创建了一个新的、普通的函数重载”,不知道这应该是什么意思)?

对不起,这太令人费解了。请不要错过language-lawyer 标签,我想知道为什么/标准的哪些部分会这样,而不仅仅是什么。

PS:为了确保我再次检查,三个常见的嫌疑人在没有调用bar 时都编译了这个示例而没有大的抱怨:https://godbolt.org/z/Wcsbc5qjv

【问题讨论】:

  • AFAIK,在类模板中声明的函数的定义(朋友也不例外)只有在使用 ODR 时才真正实例化,否则只有声明被实例化。这就是为什么只要不使用 operator[] 就可以用非默认可构造类型实例化 std::map
  • @Meowmere 听起来很合理,尽管这样编译也应该没有错误(不同的是现在bar 不依赖于Tgodbolt.org/z/b8nq5abWq,但现在clang 已经在实例化时出错了的foo。那么 gcc 和 msvc 同时接受它,也许这将是下一次跟进。到目前为止,我虽然了解map::operator[] 的情况,它是类模板的成员,但这里bar 不是成员。
  • issue 似乎相关。 C++17 中添加的措辞表明 bar 的定义确实在需要时才实例化。顺便说一句,您评论中的examplefriend 无关。只是gcc在成员实例化之前不会解析模板的成员函数的定义。我不确定这两种方法是否错误。
  • @largest 嗯。有关于模板制作程序 if;ndr 没有有效的专业化(标准意味着实例化)的规则。但是如果它不是一个模板... Then0 又是一个模板的方法。无论如何,标准中有十几个 if;ndr,看看那个,它可能适用。
  • 这听起来像是概念如何提供帮助的一个很好的例子:godbolt.org/z/qK1sxq4Ts

标签: c++ templates language-lawyer friend template-instantiation


【解决方案1】:

[temp.inst]/2 类模板特化的隐式实例化会导致声明的隐式实例化,但不会导致定义、默认参数或 noexcept-specifiers 班上的……朋友……

[temp.inst]/4 ...当在需要函数定义存在的上下文中引用时,其声明是从友元函数定义实例化的函数被隐式实例化。 .

【讨论】:

  • 现在我确信当他们写“一个新的、普通的函数重载被创建”时是口语化的演讲。结合其他答案,现在事情变得更加清晰了。从某种意义上说,我是在以别人的名义写这个问题,我希望他们的疑虑也能得到解决。你能看出这是从哪个版本的标准中引用的吗?
  • @largest_prime_is_463035818 这是 C++17。 C++20 在 [temp.inst]/(3.1)[temp.inst]/5 中有类似的语言
【解决方案2】:

像这样属于模板的一部分但本身不是模板的构造被称为 templated,因为它们仍然遵循许多相同的规则(尤其是在类模板的方法和友元分别实例化的情况下) ,给每个人自己的“实例化状态”)。标准本身已经慢慢地在这种情况下使用更精确的语言,部分原因是 constexpr-if 引入了模板化的 statements(因为它们必须单独实例化,以允许仅对一个分支这样做) 即使没有语句模板。 (这些结构可能对进一步研究有用的旧术语是“temploids”。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-06-23
    • 2016-10-19
    • 1970-01-01
    • 2020-08-20
    • 1970-01-01
    • 1970-01-01
    • 2011-05-16
    相关资源
    最近更新 更多