【问题标题】:Some questions about the point of instantiation for template关于模板实例化点的一些问题
【发布时间】:2019-10-18 12:11:08
【问题描述】:

首先,一些标准的引用段落

如果在依赖于某个周围模板 Y 的模板参数的上下文中引用模板特化 X,则给定的实例化点取决于 Y 的实例化点。
如果 X 是函数模板特化,则实例化点是 Y。
如果 X 是类模板特化,则实例化点就在 Y 的实例化点之前。

否则,给定的实例化点与包含引用 X 的语句的命名空间范围声明/定义 (D) 的位置相关联。
如果 X 是函数模板特化,则实例化点紧接在 D 之后。
如果 X 是类模板特化,则实例化点就在 D 之前。

这里有一些代码

#include <iostream>
template<int N>
struct state {
    friend auto call(state<N>);
};
template<int N>
struct add_state {
    friend auto call(state<N>) {
        return N;
    }
};
template<typename T, int N>
T show() {
    add_state<N> d;
    return T{};
}
template<typename T,int N>
class data {
public:
    T c = show<T,N>();
};
#1,#3,#2
int main() {
    data<int, 0> t;
    call(state<0>{});
}

所以,根据上面的规则,当实例化类data&lt;int, 0&gt;时,那么实例化的点就在#1。

那么show&lt;T,N&gt; 依赖于模板类数据的模板参数。所以show&lt;int,0&gt; 的实例化点在#2。

那么add_state&lt;N&gt; 依赖于模板函数show 的模板参数。所以根据规则,add_state&lt;0&gt; 的实例化点在#3。

在#3 auto call(state&lt;0&gt;)已经定义,call(state&lt;0&gt;{})应该被链接,但实际上编译器报错如下:

叮当声:

main.cpp:24:2: error: function 'call' with deduced return type cannot be used before it is defined
        call(state<0>{});
        ^
main.cpp:4:14: note: 'call' declared here
        friend auto call(state<N>);
                    ^
1 error generated.

g++:

main.cpp: In function ‘int main()’:
main.cpp:24:17: error: use of ‘auto call(state<0>)’ before deduction of ‘auto’
  call(state<0>{});
                 ^ 

为什么?我对实例化点的理解是否有一些错误? 如果不是,为什么编译器会报告这些错误?

【问题讨论】:

  • 由于show是一个函数模板,它重用了#1,而#3在它之前。
  • 警告:有状态元编程已被委员会condemned
  • 函数模板特化在翻译单元[temp.point]/5的末尾也有一个实例化点。如果show&lt;int,0&gt; 在翻译单元的末尾被实例化,call(state&lt;0&gt;) 的定义将在call(state&lt;0&gt;{}); 的使用之后。 [temp.point]/5 还禁止“两个不同的实例化点[to]根据单定义规则赋予模板特化不同的含义”,虽然我不知道这是否包括这个场景。
  • 如果您使用show 的占位符返回类型来强制实例化主体,则 GCC 和 Clang 都可以正常编译:godbolt.org/z/jEuQ_k
  • @DavisHerring 是的,#1 和 #2 是同一个 poi

标签: c++ templates


【解决方案1】:

根据[temp.inst]/2.1,隐式实例化类模板时,只实例化友元的声明:

类模板特化的隐式实例化导致声明的隐式实例化,而不是类成员的定义、默认参数或 noexcept 说明符的隐式实例化函数、成员类、作用域成员枚举、静态数据成员、成员模板和朋友

所以在#3 auto call(state&lt;N&gt;) 仅被声明。此外,普通的非限定名称查找也找不到此声明。

尽管如此,我认为它不会使您的代码格式不正确。您的代码是如此奇怪,以至于标准委员会成员或编译器实现者可能从未考虑过这种情况:通常内联友元函数在类中定义,使友元函数通过 ADL(参数相关名称查找)可见。这当然也是编译器所不具备的。

因此,在main 内部的call(state&lt;0&gt;{}),ADL 在state 的定义中找到了call 的声明,编译器只是不考虑以某种方式寻找该函数的潜在定义无关类add_state。所以无法推导出auto

【讨论】:

  • [temp.inst]/4 说“当在需要函数定义存在的上下文中引用时,其声明是从友元函数定义实例化的函数将被隐式实例化。”定义的声明应该是从add_state&lt;0&gt;实例化的,所以我认为它应该在main的引用上实例化。 does work 如果 add_state&lt;0&gt; 事先直接隐式实例化。所以我不认为定义本身有问题。
  • @uneven_mark。为什么你认为add_state&lt;0&gt; d; 需要call 的定义才能存在?
  • 我没有。我认为add_state&lt;0&gt; 通过 [temp.inst]/2 将call 的声明实例化到全局命名空间中,因为它包含它的友元定义。然后main 中的call(state&lt;0&gt;{}); 需要函数定义(在通过ADL 找到之后),因此通过[temp.inst]/4 函数定义应该从add_state&lt;0&gt; 中的定义实例化。我的假设是 callstate&lt;0&gt;add_state&lt;0&gt; 中的声明是指同一个函数。
  • @uneven_mark 所以你我之间的解释差异是由于术语 context ([temp.inst]/4 * ... 在上下文中引用。 ..*)。对你来说,这是 main 的完整定义。对我来说,上下文只是一个完整的表达或更少。
  • 我不确定我是否遵循这种区别。您认为我链接的编译示例格式不正确吗?在我的解释中,add_state&lt;0&gt;call(state&lt;0&gt;) 的准确引用方式并不重要,只要正确排序即可。例如,您可以将 add_state&lt;0&gt; t; 移动到 main 之前,或者将它们组合成一个完整的表达式:works with instantiation through add_state&lt;...&gt; but not data&lt;int, ...&gt;
【解决方案2】:

我对这件事不太自信,但希望这可能会证明有用,我整理了另一个工作示例,而不是已经建议的示例:

#include <iostream>

// forward declaration of the
// add_state template
template<int>
struct add_state;


template<int N>
struct state {
    // Note: we generate the state here
    // so that the compiler will see the
    // definition of the call function
    add_state<N> t;
    friend auto call(state<N>);
};
template<int N>
struct add_state {
    friend auto call(state<N>) {
        return N;
    }
};


int main() {
    auto val = call(state<42>{});
    std::cout << val << std::endl;

    return 0;
}

我不确定这是否会有所帮助。但我希望如此,就像我一样,我也会对一个好的解释感兴趣。

【讨论】:

  • 是的,据我所知,如果在实例化序列中只有类模板特化而没有函数模板特化,它总是有效的,这就是为什么我假设关于实例化点的特殊规则函数模板是罪魁祸首。
【解决方案3】:

您的问题在这里:

template<int N>
struct state {
    friend auto call(state<N>);//<--no way of telling return type !
};

编译器完全无法告诉call 函数返回什么,因此不得不放弃。 修复也很明显,只需给它一些可以使用的东西,例如:

friend auto call(state<N>) {return N;}

【讨论】:

  • 我认为这不是问题所在。 (@jackX 请纠正我。)显然,如果直接提供定义,它将起作用。但是通常提供一个带有占位符返回类型的声明并在以后定义它是完全可以的,只要定义发生在需要返回类型之前。 add_state&lt;0&gt; 中的定义引用了相同的函数,并且应该在需要 call(state&lt;0&gt;{}); 时实例化(并推导返回类型),假设 add_state&lt;0&gt; 中的声明已经被实例化。
  • 您可以通过在两个声明中将其替换为某个固定类型来查看返回类型不是问题。然后程序将失败并出现链接器错误。如果在调用之前显式实例化add_state&lt;0&gt;,也不会出现错误。 (返回类型中是否有占位符)
  • @uneven_mark 是的,你是对的,模板类状态的目的是生成调用的声明,模板类add_state是生成调用的定义,adl可以在类状态中查找调用,如果定义被激发,则调用函数将被链接
  • @uneven_mark 另外,我已经在问题下的评论中给出了解释
【解决方案4】:

如果使用int 而不是auto 会出错:

main.cpp:15: undefined reference to `call(state<0>)'
collect2.exe: error: ld returned 1 exit status

{return N;} 添加到friend int call(state&lt;N&gt;) 时,效果很好。 然后把int换回auto,也可以。

【讨论】:

  • 添加 {return N ;} 到哪个调用?您是否将函数调用的声明更改为定义?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-07-23
  • 2016-10-11
  • 2011-06-17
  • 1970-01-01
  • 2021-11-21
  • 1970-01-01
  • 2017-01-29
相关资源
最近更新 更多