c++ 模板参数包什么时候推导为空?

ffscu2ro  于 2023-03-20  发布在  其他
关注(0)|答案(1)|浏览(215)

bounty将在6天后过期。回答此问题可获得+50声望奖励。Brian Bi希望引起更多人关注此问题。

考虑以下示例(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. varidic])将被导出为一个空的模板参数序列。...
然而,在对g的调用中,T...另外 * 出现在推导上下文 * 中,这导致推导被 * 尝试并失败 *,这似乎导致g变得不可用。一旦推导被尝试并失败,似乎没有“后退”到T...为空。

  • 这种行为是故意的吗?如果是,为什么?
  • 如果是,“未以其他方式推导”的措辞是否指定了此行为?(* 即 *,它意味着空回退仅在包未出现在推导上下文中时发生)
  • 如果是这样的话,这个措辞是否足够清楚?“没有以其他方式推导”的另一种似乎合理的阅读是“要么没有进行推导,要么尝试推导但失败了”。
rseugnpd

rseugnpd1#

...一个未被导出的尾随模板参数包([temp. varidic])将被导出为一个空的模板参数序列。...
可以说,* 没有以其他方式推导 * 不是一个应该以某种方式或自动放松一些其他规则的子句,或者实际上它与为什么它是畸形的无关(与我认为你所暗示的相反)。
这个“另一个规则”也许可以通过另一个非常简单的例子得到最好的证明:

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

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

上面的代码编译失败,为什么?因为即使short无缝地转换为intpromotion),它也不是同一类型。
此外,由于nullptr只有std::nullptr_t的一些 plain 类型-它非常不适合参与模板参数推导 *。
所以,让我们暂时忘掉非推导的语境,试试推导的语境:

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

int main() {
    S<> s1;
    g(&s1, nullptr);
}
  • 如果你愿意,就 *
int main() {
    S<> s1;
    g(&s1, 0);
}

并且两者都由于相同的原因而失败。
现在,如果您希望允许转换-那么使用身份模板-这甚至可以用于非推导上下文!
对于您的情况,示例可能类似于(c++2a):

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

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

这是有效的。(请注意,如果您没有c++20,只需自己编写身份模板)
正如评论中所说,把问题转过来也许会引出一个更有趣的问题?

  • 允许在非演绎的上下文中使用空模板参数演绎的理由是什么?*

相关问题