【问题标题】:When to supply default arguments as template arguments何时提供默认参数作为模板参数
【发布时间】:2020-07-13 11:49:09
【问题描述】:
template<typename T, typename U = T>
struct Test{};
template<typename T>
void func(Test<T>){  //#1
}
int main(){
  func(Test<int>{});  //#2
}

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

在dcl.fct.default#1

如果在参数声明中指定了初始化子句,则该初始化子句用作默认参数。默认参数将用于缺少尾随参数的调用。

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

如Test&lt;T&gt;#1。也许在#1,专业化是Test&lt;T,T&gt;,这只是推理。但是,标准中没有正式的术语明确描述这一点。

将默认参数作为模板参数的唯一引用在以下规则中:
temp#arg-8

当 simple-template-id 没有命名函数时,默认模板参数在需要该默认参数的值时隐式实例化。 [ 示例:
模板 struct S { };
S* p; // p 的类型是 S*
U 的默认参数是实例化以形成类型 S*。 — 结束示例 ]

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

【问题讨论】:

  • @StoryTeller-UnslanderMonica 在写这个问题之前我已经读过了。我的阅读是引用只是关于默认参数将在什么情况下隐式实例化
  • 不只是。例子并非巧合。
  • 我实际上没有。该标准的读者能够理解“在需要时”的情况包括未提供显式参数的情况。它还可能包含更多内容(我认为这是设计使然)。
  • 函数模板定义没有实例化任何东西。这会在调用站点的模板参数推导过程中稍后发生。这可能是规范性文本与“需要时”相配的原因。

标签: c++ language-lawyer


【解决方案1】:

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

一个模板ID是有效的,如果

  • 对于没有默认模板参数的每个不可演绎的非包参数都有一个参数,

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

它。如果您不做出任何不同的选择,某事将自动发生或出现的方式,尤其是在计算机上 -- cambrige online dictionary

【讨论】:

  • 我不认为这句话意味着太多,我认为它只是说必须为这些非推导的模板参数显式提供一个参数,以使模板 ID 有效。另外dcl.fct.default已经明确指定了函数调用何时提供默认参数,何不为模板默认参数加一句类似的语句。
  • @JackX 我想他们认为这很明显,以至于他们可能甚至没有注意到缺少这句话。
  • 我更倾向于将其视为缺陷,因为在标准中,对于何时为函数调用和function template parameter提供默认参数,有相应的规则,只有类模板没有。
  • @jackX 是的,这也是我的答案……(点击明确的词)我认为标准中有很多这样的漏洞。我相信,即使是最精确构建的数学理论,也是基于某种难以识别的“先验”形式。但是对于另一个相关的问题,即使是“先验”的标准对我来说也不清楚,也许我用可能的习惯来填补漏洞。
  • 但是,正如你所说。为了使模板 ID 有效,需要默认参数。这将使temp#argu 自相矛盾。因为在 #1 处,不会发生任何实例化,因为此时实体只是模板定义。
【解决方案2】:

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

关键是需要实例化和引用时的区别。在来自[temp.arg]/8 的示例中,U 的默认模板参数被实例化,因为 它是S&lt;bool&gt; 的实例化所需要的,它明确地解析为S&lt;bool, int&gt;。在 OP 自己的示例中,U 的依赖于实例化的默认模板参数的实例化将仅遵循 after 重载决议将 void func(Test&lt;T&gt;) 解析为最佳可行重载,从而实例化它。然而,仅仅检查候选重载作为重载决议的一部分,不会导致任何候选或任何函数模板候选参数的实例化,这些参数本身就是模板。但是,它将引用所述模板参数作为重载解决过程的一部分(丢弃不可行的候选者)。


来自[temp.deduct]/1 [强调我的]:

当一个函数模板特化被引用时,所有的模板参数都应该有值。值可以明确指定或,在某些情况下,可以从使用中推导出来或从默认获取 模板参数。 [...]

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

来自[temp.deduct]/2 [强调我的]:

当指定显式模板实参列表时,模板实参必须与模板形参列表兼容,并且必须产生如下所述的有效函数类型;否则类型扣除失败。具体来说,在针对给定函数模板评估显式指定的模板参数列表时,会执行以下步骤:

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

来自[temp.deduct]/6 [强调我的]:

在模板参数推导过程中的某些点,有必要采用一个使用模板参数的函数类型,并将这些模板参数替换为相应的模板参数。当任何显式指定的模板参数被替换为函数类型时,在模板参数推导的开头完成,并且在任何模板参数被替换时再次在模板参数推导的末尾从默认参数推导出或获得的参数被替换。

很明显,作为“模板参数推导结束”的一部分,任何没有明确指定的模板参数都将从默认参数推导或获取并代入候选函数在丢弃或将其列为可行的重载之前。这适用于与实例化没有任何关系,并且仅在给定的特化(可能尚未或永远不会被实例化)被引用的上下文中,作为重载决议的一部分。

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

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

  • [...]
  • (3.3) 类型是类模板的特化(例如,A&lt;int&gt;)包括类型、模板和非类型值由特化的模板参数列表引用。

我们注意到,作为类模板特化的函数模板形参的函数模板实参推导的一部分,函数模板形参类型(用于对实参进行推导)包括模板引用的类型模板形参的实参列表,表示特定的类模板特化(函数模板实参)被引用,因此,按照[temp.deduct]/1,它将通过模板对其任何未明确指定的模板参数进行参数推导和默认参数检查。

【讨论】:

  • 你的意思是,模板实参推导首先推导出模板实参为T,即函数模板的template-parameter,然后推导过程进入the end of template argument deduction,即这些推导的参数,或从函数模板声明的默认参数(如果有)中获得的参数被代入函数类型,代入的参数就像Test&lt;int&gt;,然后temp#arg-8规则执行这样的template-id和最终结果是Test&lt;int,int&gt;,对吗?
  • @jackX 除了最后一部分之外,我会同意一切:[temp.arg]/8 与 “何时将默认参数作为模板参数提供” 无关,它只有在实际找到可行的候选者的情况下,才在重载决议完成之后。请注意,我上面的回答中的所有内容仅涵盖重载解决方案,以及在尝试查找 候选函数 是否实际上是可行的候选函数时如何应用函数模板参数推导。后者,如果可行的候选人被评为最佳,将导致函数的实例化,因此也......
  • ... 默认模板参数,但这在这里不相关,因为我们只是研究重载解析的模板参数推导部分,它本身并没有实例化任何东西,它只是检查候选人(“参考”他们,如果你愿意的话)。因此,您质疑默认模板 arg 的实例化。并且我们正在考虑的主要模板定义是正交的 - 默认模板 arg。类模板规范。只有当函数模板被重载决议实际选择并实例化时,才需要实例化。
  • Afacs [temp.deduct.type]/3(见我上面回答的最后一部分)是这里的关键,而不是 [temp.arg]/8。同样,在重载决议完成之前,我们不会考虑任何实例化的需要,并且上面引用的相关段落都适用于作为重载决议的一部分的参数推导在它完成之前 .我们想分析一个候选函数是如何被认为是可行的。如果它最终被选中并实例化,那么 Test&lt;int, int&gt; 将与 ("as needed" by [temp.arg]/8) 一起实例化。
  • 是的,我同意你的说法,我上面的评论中的the final resulting is Test&lt;int,int&gt; 表示在重载决议选择了函数特化作为被调用函数之后,然后将该参数实例化为Test&lt;int, int&gt;。你同意吗?
猜你喜欢
  • 2016-11-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-23
  • 2013-02-28
相关资源
最近更新 更多