【问题标题】:When are template parameter packs deduced as empty?什么时候模板参数包被推断为空?
【发布时间】:2019-12-02 02:30:48
【问题描述】:

考虑以下示例 (Coliru link):

template <class... T> struct S { using type = int; };

template <class... T>
void f(typename S<T...>::type) {
    static_assert(sizeof...(T) == 0);
}

template <class... T>
void g(typename S<T...>::type, S<T...>*) {}

int main() {
    f(42);
    g(42, nullptr);
}

GCC 和 Clang 都对调用 f 感到满意,但对调用 g 却不满意。

在对f 的调用中,虽然T... 出现在非推导上下文中,但最终推导为空。这似乎是由于[temp.arg.explicit]/4

... 没有以其他方式推导的尾随模板参数包 ([temp.variadic]) 将被推导为模板参数的空序列。 ...

然而,在对g 的调用中,T...出现在推断的上下文中这一事实似乎会导致推断尝试并失败导致g 变得不可行。一旦尝试扣除但失败,似乎没有“回退”到T... 为空。

  • 这种行为是有意的吗?如果是,为什么?
  • 如果是这样,“未以其他方式推断”的措辞是否旨在指定此行为? (,这意味着只有当包出现在没有推导的上下文中时才会出现空回退)
  • 如果是这样,这个措辞是否足够清楚?似乎“未以其他方式推断”的另一种说法是“要么没有进行演绎,要么尝试了演绎但失败了”。

【问题讨论】:

  • 我认为这是有意为之,因为我想不出可能的替代阅读方式。
  • 完全没有模板:void f(int*) { } void f(double*) { } void g() { f(nullptr); } - 对我来说似乎是一致的(我们真正想要使用模板产生的许多重载中的哪一个?)。我宁愿把问题转过来:在非推导上下文中允许空模板参数推导的原因是什么?
  • @Aconcagua 可能与在非推断上下文中采用默认模板参数的原因相同godbolt.org/z/XjG_D1
  • @Rakete1111 所以空参数列表会被视为“默认”吗?以某种方式看起来很合理......
  • @Brian 看来 nullptr_t 是类型推断系统的死胡同。它无法从 nullptr 转到“指向某种 S 的指针”。作为一种可能的解决方法,您可以提供一个提示: template S* sp_conv(nullptr_t zp) { return zp; } ;然后调用 g(42, sp_conv(nullptr));这适用于 GCC v8 和 Clang v7。

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


【解决方案1】:

...尾随模板参数包([temp.variadic]),否则 deduced 将被推断为模板参数的空序列。 ...

可以说 not else deduced 不是一个应该以某种方式或自动放松一些其他规则的子句,或者实际上它与为什么它的格式错误无关(与什么相反我想你暗示)。

这个其他规则也许最好用另一个非常简单的例子来说明:

template<class T>
void f(T, T){};

int main() {
    f(int{42},short{42});
}

以上编译失败。为什么?因为即使short 无缝转换为int促销),它也不是同一类型。

另外,因为nullptr 只是有点plain 类型的std::nullptr_t - 它非常不适合参与模板参数推导根本。 p>

所以让我们暂时忘记非推断上下文,并尝试使用推断上下文:

template <class... T>
void g(S<T...>*, S<T...>* ) {}

int main() {
    S<> s1;
    g(&s1, nullptr);
}

或者,如果您愿意,也可以

int main() {
    S<> s1;
    g(&s1, 0);
}

由于相同的原因,两者都失败了。

现在,如果您想允许转换 - 然后使用身份模板 - 这甚至适用于非推断上下文!

对于您的情况,示例可能类似于 ():

template <class... T>
void g(typename S<T...>::type, std::type_identity_t<S<T...> >*) {}

int main() {
    f(42);
    g(42, nullptr);
} 

这是有效的。 (注意如果没有自己写身份模板)

正如comment 中所述,将问题转过来可能会引出一个更有趣的问题?

在非推导上下文中允许空模板参数推导的原因是什么?

【讨论】:

  • 听起来你的意思是,只有在给定模板参数(或模板参数包)的每个推导上下文中推导成功时,整体推导才会成功;如果同一个模板参数有两个推导上下文,并且两个上下文之一推导失败,则不根据另一个上下文推导该参数;相反,扣除只是失败了。但是,我认为您没有回答我的问题。不能将“未以其他方式推断”理解为涵盖这种情况吗?从某种意义上说,这个包没有被推断出来,因为推断失败了。
  • 我猜你是对的,如果在真空中阅读它有点不清楚。也许我倾向于,但我确实将其读为“未以其他方式推断”!=“尝试进行推断但失败”和“未以其他方式推断”==“实际上并未进行推断”。那主要是由于那里的“否则”。目前尚不清楚,但可能还有改进的余地。
  • @darune 我会质疑为什么标准会使用主要是英国的“其他方式”(意思是“以其他方式”)
猜你喜欢
  • 2023-03-06
  • 2012-09-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-09
相关资源
最近更新 更多