是否所有有效的模板都已实例化?

时间:2018-05-26 01:04:08

标签: c++ templates

当我有多个可以实例化的模板时,会发生什么?

template <typename T> void foo(T*);
template <typename T> void foo(T**);

template void foo(int**);

template <typename T> void foo(T*) { printf("called foo(T*)\n"); }
template <typename T> void foo(T**){ printf("called foo(T**)\n"); }

当我们需要实例化一个foo(int **)时,任何一个实例都可以工作,一个用T = int *,另一个用T = int **。实际上,目标文件只包含一个,即T **。另一方面,如果我将static_assert(false)放入未使用的版本中,则编译失败。这是否意味着它 实例化了?

事实证明,T **是我想在这种情况下使用的那个。但即使两者都被实例化,重载决策也会选择T **,所以这对我来说都是有用的。

现在在我的实际代码中,我请求两个实例:

template void foo(int*);
template void foo(int**);

实例化foo<int>(int**)foo<int>(int*),因此我获得了两个模板。后者是强制的,因为template <typename T> foo(T**)不匹配。但我不明白为什么我不能为前者获得foo<int*>(int**)的规则。或者foo<int*>(int**)以及foo<int>(int**)

是的,如果我需要强迫它,我知道我可以做template void foo<int*>(int**)

这一切让我意识到我并不真正理解当编译器看到需要模板实例化的调用时会发生什么。它是否会扫描模板,直到找到有效的模板,然后停止?或者它是否实例化所有有效选项(然后从目标文件中丢弃不需要的选项)?

2 个答案:

答案 0 :(得分:4)

执行模板参数推导以确定template void foo(int**);命名的函数模板特化;它在考虑部分排序后选择T**一个。请参阅[temp.deduct.decl]及其中的相关章节。这与用于选择在重载解析期间调用的函数模板的过程大致相同(存在一些细微差别)。

没有用于实例化匹配的所有内容的机制。如果编译器无法选择唯一的函数模板特化,那么程序就是格式不正确。

  

另一方面,如果我将static_assert(false)放入未使用的版本中,则编译失败。这是否意味着它被实例化了?

像往常一样,那只是[temp.res]/8正在工作。以不依赖于模板参数的方式形成错误的代码可以在没有实例化的情况下立即诊断出来。

答案 1 :(得分:1)

  

如果我将static_assert(false)放入未使用的版本中,则编译失败。这是否意味着它被实例化了?

没有。编译器对模板定义进行一些基本检查。定义中的所有内容都必须是有效的C ++,但是在知道模板参数的实际实例化之前,无法检查依赖于模板参数的某些内容。由于static_assert(false);不依赖于任何模板参数,因此它总是会导致错误,即使模板从未实例化,也允许编译器记录错误。

如果你真的想要一个永远不应该实例化的模板,通常的方法是使用=delete作为函数模板,或者保持类模板未定义。

  

但是我不明白为什么我不能为前者获得foo<int*>(int**)的规则。

在命名函数模板特化的大多数上下文中,规则是任何比所有其他可行重载“更专业”的模板都是使用的模板。 “更专业化”的确切定义有点棘手,但基本思想是,如果函数模板“A”可以采用的任何参数列表也可以由函数模板“B”采用,反之则不然,然后“A”更专业。在你的例子中

template <typename T> void foo(T*);  // #1
template <typename T> void foo(T**); // #2

如果参数arg对任何类型U**都有U类型,那么foo(arg)的模板#2的模板参数扣除可以成功T=U,我们可以看到foo(arg)的模板#1的模板参数推导也可以使用T=U*成功。另一方面,显然另一个参数arg2可能意味着foo(arg2)的模板参数可以成功用于模板#1但是对于模板#2失败,例如arg2具有类型int*。因此,功能模板#2比功能模板#1更专业。这意味着像foo(arg)这样的表达式,只要两次成功的类型推导(并且没有涉及其他可行的重载),就意味着使用模板#2。

相同的“更专业”规则适用于您的显式实例化

template void foo(int**);

与函数调用表达式非常相似,编译器将使用模板参数推导来查看您提供的声明是否与每个函数模板匹配。在这种情况下,两者都成功,但由于模板#2更专业,声明被解释为#2的特化,而不是#1。

  

这一切让我意识到我并不真正理解当编译器看到需要模板实例化的调用时会发生什么。它是否会扫描模板,直到找到有效的模板,然后停止?或者它是否实例化所有有效选项(然后从目标文件中丢弃不需要的选项)?

大多数使用重载函数模板名称的粗略序列是:

  1. 进行名称查找以确定要考虑的功能模板和功能列表。

  2. 对于列表中的每个函数模板,尝试模板参数推导。如果模板参数推断失败,或者将任何模板参数替换为函数类型失败,请从此处忽略该模板。如果成功,则会为每个模板参数生成一个模板参数,并为该专业化生成一个特定的函数类型。

  3. 在最初查找的非模板函数集合与上面确定的函数模板特化集合相结合时,重载解析的方式与非模板的解析方式大致相同。但是如果两个候选函数不能被命令说一个是比另一个更好的重载只是基于参数类型和所涉及的隐式转换,那么非模板函数胜过函数模板特化,而来自更专业的函数模板的特化从不太专业化的功能模板中击败专业化。

  4. 请注意,在此过程中,将实例化函数类型,但不实例化函数模板特化的定义。