【问题标题】:Point of instantiation of default arguments in a template function模板函数中默认参数的实例化点
【发布时间】:2020-07-10 20:33:37
【问题描述】:

当从非模板上下文引用函数模板时,该标准允许在封闭的命名空间范围声明之后或在翻译单元的末尾实例化函数模板:[temp.point]/1

对于函数模板特化,成员函数模板 特化,或成员函数或静态的特化 类模板的数据成员,如果特化是隐式的 实例化,因为它是从另一个模板中引用的 专业化和引用它的上下文取决于 模板参数,特化的实例化点 是封闭特化的实例化点。 否则,这种特化的实例化点紧跟在命名空间范围声明或定义之后 指专业化。

[temp.point]/8

函数模板的特化,成员函数模板, 或类模板的成员函数或静态数据成员 在翻译单元中有多个实例化点,并且 除了上述实例化点之外,对于任何 这种特殊化在 翻译单元,翻译单元的结尾也被认为是一个 实例化点。 类模板的特化在 翻译单元内的大多数实例化点。一种 任何模板的特化都可能有实例化点 多个翻译单元。如果两个不同的实例化点 根据模板特化赋予不同的含义 单定义规则,程序格式错误,无诊断 必填。

现在考虑这个最小的可重现示例:

#include <iostream>
#include <array>
struct A {};
std::array<char, 2> show(float, A)
{
    std::cout << "2\n";
    return {};
}
template<typename T>
struct Fun {
    decltype(show(0, T{})) b;
};
template <typename T>
void func(T, int c = sizeof(Fun<T>{}.b))
{
    show(0, T{});
    std::cout << c << '\n';
}
int main()
{
    func(A{});
}
char show(int, A)
{
    std::cout << "1\n";
    return {};
}

GCC 和 Clang 都输出 1 2 (godbolt)。

这里,func&lt;A&gt; 的实例化(在main 中触发)有两个实例化点:一个紧跟在main 之后(因此在第二个show 之前),另一个在翻译单元的末尾。第一个1 表示编译器在翻译单元的末尾实例化func&lt;A&gt;。但是,默认参数sizeof(Fun&lt;T&gt;{}.b) 导致Fun&lt;A&gt; 被实例化,第二个2 表明Fun&lt;A&gt; 在第二个show 之前实例化。

现在,默认参数的实例化点被指定为func&lt;A&gt;[temp.point]/2

如果类模板的函数模板或成员函数是 以使用默认参数定义的方式调用 那个函数模板或成员函数,要点 默认参数的实例化是 函数模板或成员函数特化。

嗯...这似乎表明这两个数字应该相同。

我觉得我在这里遗漏了一些东西。有没有我碰巧忽略的细节?还是我犯了错误?

【问题讨论】:

    标签: c++ templates language-lawyer instantiation name-lookup


    【解决方案1】:

    正如问题中引用的[temp.point]/8 所说:

    如果两个不同的实例化点根据单一定义规则赋予模板特化不同的含义,则程序是非良构的,不需要诊断。

    根据单定义规则,如果定义中使用的名称的函数调用重载解析将产生定义之外定义的不同实体,则两个定义不相同。 ([basic.def.odr]/6.2)

    func&lt;A&gt;Fun&lt;A&gt; 中对show 的两次调用的重载解析将根据func&lt;A&gt; 的实例化点是紧接在main 之后还是在翻译单元,两者都是允许的实例化点。

    因此程序格式错误,不需要诊断

    【讨论】:

    • 但是当你注释掉std::array&lt;char, 2&gt; show(float, A)函数时,你如何解释func&lt;A&gt;中的show 可以被重载,但是Fun&lt;A&gt;中的show格式错误,如果Fun&lt;T&gt;{}func&lt;A&gt; 处于同一实例化点,func&lt;A&gt; 中的 show 也会不正确。
    • @jackX 我不确定我是否正确理解了您的评论。如果我没有,也许将其作为一个新问题发布。如果删除第一个show 重载,那么我会说程序仍然格式错误,不需要诊断,因为如果func&lt;A&gt; 的实例化点直接在main 之后,那么show 的两个查找都将失败,但如果它位于翻译单元的末尾,则两者的查找都会成功(因为Fun&lt;A&gt; 的实例化点紧邻func&lt;A&gt; 的实例化点之前)。所以我认为引用的段落似乎再次适用。
    • 对不起,我想你没有明白我的意思。其实我想表达的是注释掉std::array&lt;char, 2&gt; show(float, A)int c = sizeof(Fun&lt;T&gt;{}.b),只留下show(0, T{});内部func ,编译器会选择char show(int, A)show(0, A{});,证明@的实例化点987654351@总是在翻译单元的末尾,否则程序格式不正确。如果你保持int c = sizeof(Fun&lt;T&gt;{}.b)并注释掉show(0, T{});,程序就会格式不正确,看来@的实例化点987654354@ 在main 之后。
    • 所以我认为Fun&lt;A&gt;的实例化点与func &lt;A&gt;的实例化点不一样。似乎违反了标准所说的规则。
    • 如果我没有表达清楚。代码可以做。comment default argument将证明show(0, A{})的实例化点始终在翻译单元的末尾。remain default argument将证明Fun&lt;A&gt; 的实例化点总是在main 之后和char show(int, A) 之前。因此证明了我前面提到的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-11
    • 2010-09-18
    • 2013-11-27
    相关资源
    最近更新 更多