【问题标题】:Dependent non-type parameter packs: what does the standard say?相关的非类型参数包:标准是怎么说的?
【发布时间】:2019-09-04 07:22:38
【问题描述】:

我认为下面的代码格式正确:

template< typename T >
using IsSigned = std::enable_if_t< std::is_signed_v< T > >;

template< typename T, IsSigned< T >... >
T myAbs( T val );

其他人说它格式不正确,因为 C++17 标准的 §17.7 (8.3):

知道哪些名称是类型名称可以检查每个模板的语法。如果:(...) 可变参数模板的每个有效特化都需要一个空模板参数包,或 (...)

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

在我看来,IsSigned&lt; T &gt;... 是一个依赖模板参数,因此在模板定义时无法对照 §17.7 (8.3) 检查它。 IsSigned&lt; T &gt; 可以是例如 void 用于 Ts 的一个子集,int 用于另一个子集或替换失败。对于void 子集,空模板参数包确实是唯一有效的特化,但int 子集可能有许多有效的特化。这取决于实际的 T 参数。

这意味着编译器必须在模板实例化之后检查它,因为之前不知道 T。在这一点上,完整的参数列表是已知的,有零个可变参数。该标准规定如下(§17.6.3 (7)):

当 N 为零时,展开的实例化产生一个空列表。这样的实例化不会改变封闭结构的句法解释

这就是为什么我认为它的格式很好。

  • 你怎么看?
  • 如何确定这种歧义?很难决定,因为代码可以编译,但没有任何意义:§17.7 (8.3) 是 NDR,编译器不必引发任何编译错误。

【问题讨论】:

  • 在我看来,“编译器必须在之后检查它”之类的参数并不重要。标准很明确。 “可变参数模板的每个有效特化都需要一个空模板参数包”。如果您的代码确实如此,那么它的格式不正确。编译器如何在理论上检查这一点并不重要。
  • 我认为最好的做法是了解为什么这条规则是标准的一部分的原因,并检查它在这种情况下是否会导致问题。这可能最接近标准的意图。
  • @geza 抱歉,Tint 在给定代码中当然不是正确的情况。我意识到,鉴于此,我或多或少会重述原始问题(“具有从属模板参数的模板的'每个有效专业化'的集合是否受从属模板参数的约束或那些无关紧要?” ),评论已删除。
  • @MaxLanghof:是的,如果有一个专门化,使IsSigned 非无效,那么代码是格式良好的。但是,如果没有这样的专业化,那么代码就是格式错误的。至少,这是我的理解,只是严格解释所写的内容。但我不确定,这是标准作者的意图。
  • @geza 我认为这种解释可能会导致格式正确的代码无法确定。不确定这是否是一个问题......我倾向于相反的解释(有效的特化集不受依赖模板参数的约束),因此即使不存在 IsSigned 的非空特化,它是格式良好的(因为这样的IsSigned 特化可能存在)。但我仍然认为我们应该首先根据这条规则背后的意图来回答。

标签: c++ templates language-lawyer variadic-templates sfinae


【解决方案1】:

代码格式错误,不需要诊断。

如果std::is_signed_v&lt;T&gt;,那么std::enable_if_t&lt;std::is_signed_v&lt;T&gt;&gt; 表示类型void。否则,std::enable_if_t&lt;std::is_signed_v&lt;T&gt;&gt; 不表示有效类型。因此,myAbs 的每个有效特化都需要一个空模板参数包。

根据[meta.rqmts]/4,如果std::enable_if 是专用的,则程序具有未定义的行为。因此,上述行为无法更改。

在我看来IsSigned&lt; T &gt;... 是一个依赖模板参数, 因此无法根据模板中的 §17.7 (8.3) 检查它 定义时间。 IsSigned&lt; T &gt; 可以是例如 void 一个 Ts、int 的子集用于另一个子集或替换失败。为了 void 子集是真的,即空模板参数包 将是唯一有效的专业化,但 int 子集可以 有许多有效的专业。这取决于实际的T 论据。

编译器无法检查它,就像它无法为您求解任意方程一样。 NDR(不需要诊断)正是针对这种情况进行的——程序格式错误,如果编译器实际上能够检测到,则需要诊断。 NDR 允许编译器不对其进行检查。

当 N 为零时,展开的实例化产生一个空 列表。这样的实例化不会改变句法 对封闭结构的解释。

我们所说的规则是语义规则,而不是句法规则,因为句法规则在 [gram] 中。


那么 NDR 规则的基本原理是什么?一般来说,它们解决了在实施策略中不可重现的问题。例如,它们可能会导致代码在某些实现策略中出现异常,但在其他实现策略中不会导致任何问题(并且不容易)。


另外,请注意,标准在程序方面使用“格式错误”等术语进行讨论。因此,谈论孤立代码 sn-p 的良好格式并不总是合理的。在这种情况下,std::enable_if 不需要特化,否则情况可能会变得更加复杂。

【讨论】:

  • 假设一个库提供了上面的功能。我们声明它是不正确的。然后用户添加以下代码:namespace std { template&lt;&gt; struct enable_if&lt; true, void &gt; { typedef int type; }; } 它变得格式良好。我认为我们不能说它在实例化之前是良好的或不良的。
  • @Broothy 根据[meta.rqmts],用户代码是未定义的行为。
  • 基本问题是“template&lt;class T&gt; struct DefaultVoid { using T = void; }; template&lt;class T&gt; using DefaultVoidT = DefaultVoid&lt;T&gt;; template&lt;class T, DefaultVoidT&lt;T&gt;...&gt; void foo(T) {}; 格式是否正确?”,隐含“如果不是,添加template&lt;&gt; struct DefaultVoid&lt;int&gt; { using T = int; } 后格式是否正确?”。如果你愿意,我(或@Broothy)可以提出一个新问题,但“除非允许,否则不要专门化 std 的东西”与核心问题完全无关(另见上面的评论)。
  • 加上你有我的支持!因此,如果我们添加一个简单地包装 std::enable_if/std::enable_if_t 的中间模板(其中中间模板理论上可以专门化,但库禁止任何人专门化),这个程序将变得结构良好?
  • @MaxLanghof 是的。虽然如果有禁止专业化的规则,“格式良好”可能属于库定义的“未定义行为”类别:)
猜你喜欢
  • 2012-12-20
  • 1970-01-01
  • 2022-06-10
  • 1970-01-01
  • 2018-05-01
  • 1970-01-01
  • 2012-06-19
  • 2014-08-19
  • 1970-01-01
相关资源
最近更新 更多