何时提供默认参数作为模板参数

When to supply default arguments as template arguments

本文关键字:参数 默认 何时提      更新时间:2023-10-16
template<typename T, typename U = T>
struct Test{};
template<typename T>
void func(Test<T>){  //#1
}
int main(){
func(Test<int>{});  //#2
}

标准中似乎没有规则提到模板参数需要默认参数的情况。

In dcl.fct.default#1

如果在参数声明中指定了初始值设定项子句,

则此初始值设定项子句将用作默认参数。默认参数将在缺少尾随参数的调用中使用。

在本节中,规则显式描述何时为函数调用提供默认参数。但是,我还没有在标准中找到与上述句子类似的引用,该引用描述了何时提供默认参数作为模板参数。

比如Test<T>#1.也许在#1,专业化会Test<T,T>,只是推断。但是,标准中没有正式的术语明确描述这一点。

唯一暗示默认参数作为模板参数的引号是以下规则:
temp#arg-8

当简单模板 id 不命名函数时,当需要默认参数的值时,将隐式实例化默认模板参数。[ 示例:
templatestruct S { };
S* p; p 的类型是 S<bool,>*
U 的默认参数被实例化以形成类型S<bool,>*。  — 结束示例 ]

考虑#1处的代码,默认参数是否需要在#1?如果是(似乎在这一点上需要,因为如果我不为模板参数 U 指定默认参数,那么在这一点上#1会发生错误。 参见 Godbolt 结果),根据上面的引用,默认的模板参数需要隐式实例化,但是在这一点上,T是一个模板参数,并且这种函数模板的定义什么都没有实例化(它只是一个函数模板定义点)。那么,这句话是如何解释这一点的呢?

[temp.names]里面也有这样一段:

模板 ID 有效,如果

  • 每个没有默认模板参数的不可推导非包参数都有一个参数,

因此,可以认为需要默认参数意味着需要默认参数才能使模板 id 有效,并且当缺少模板参数时,该参数是默认参数值。但是我在类模板的标准中没有找到任何明确的内容。对于函数模板,这更明确。可能没有人指出标准中的这个漏洞,因为这是一种常见的模式:默认用于代替用户未提供的内容。也许它不会改变,因为英语词典中默认的定义已经给出:

某事自动发生或出现的方式,尤其是在计算机上,如果您不做出任何不同的选择 -- 坎布里奇在线词典

根据上面的引用,默认的模板参数需要隐式实例化,但是在这一点上,T是一个模板参数,并且这种函数模板的定义什么都没有实例化(此时它只是一个函数模板定义)。那么,这句话是如何解释这一点的呢?

关键是实例化需求引用时间之间的区别。在 [temp.arg]/8 的示例中,U的默认模板参数被实例化,因为它是实例化S<bool>所必需的,这明确地解析为S<bool, int>。在 OP 自己的示例中,只有在重载解析解析为最佳可行重载并因此实例化它之后,才会实例化依赖于U的实例化默认模板参数void func(Test<T>)。但是,仅检查候选重载作为重载解析的一部分,不会导致实例化任何候选项或任何函数模板候选参数(这些参数本身就是模板)。但是,它将引用所述模板参数作为重载解决过程的一部分(丢弃不可行的候选参数)。

<小时 />

从[温度扣除]/1[强调我的]:

引用函数模板专用化时,所有模板参数都应具有值。这些值可以显式指定,或者在某些情况下,可以从使用中推断出来,或者从默认模板参数中获取。[...]

对于 OP 的特定示例,前者(实例化)仅在重载解析完成并且实例化了最佳可行重载(进而实例化类模板函数参数)之后发生。然而,后者(引用的函数/类模板专用化)也适用于重载解析期间,即使丢弃不可行或不是最佳可行匹配的候选项,这些候选项永远不会导致实例化。

从[温度扣除]/2 [强调我的]:

指定显式模板参数列表时,模板参数必须

与模板参数列表兼容,并且必须生成有效的函数类型,如下所述;否则类型推断失败。具体而言,在计算与给定函数模板相关的显式指定的模板参数列表时,将执行以下步骤:

  • (2.1)指定的模板参数必须与实物模板参数匹配(即类型、非类型、模板)。参数不能多于参数,除非 [...]

从[温度扣除]/6[强调我的]:

在模板参数

推导过程中的某些点,有必要采用使用模板参数的函数类型,并将这些模板参数替换为相应的模板参数。当任何显式指定的模板参数被替换到函数类型中时,在模板参数推导开始时完成此操作,当从默认参数推导或获取的任何模板参数被替换时,在模板参数推导结束时再次执行此操作。

很明显,作为"模板参数推导结束">的一部分,任何未明确指定的模板参数都将从默认参数中推导或获取,并在丢弃或将其排序为可行重载之前替换到候选函数中。这适用于与实例化没有任何关系,并且仅在作为重载解析的一部分引用给定专用化(可能尚未或永远不会实例)的上下文中适用。

最后,从 [temp.deduct.type]/3 [强调我的]:

给定的类型P可以由许多其他类型、模板和非类型值组成:

  • [...]
  • (
  • 3.3)类模板的专用化类型(例如,A<int>)包括专用化的模板参数列表引用的类型,模板和非类型值。

我们注意到,作为函数模板参数的函数模板参数推导的一部分,函数模板参数类型(用于对参数的推导)包括模板参数的模板参数列表所引用的类型,这意味着引用了特定的类模板专用化(函数模板参数),以便根据 [temp.deduct]/1,它将对未显式指定的任何模板参数进行模板参数推断和默认参数检查。