何时将模板参数包推导出为空?

Bri*_*ian 14 c++ templates language-lawyer variadic-templates

考虑以下示例(Coliru链接):

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);
}
Run Code Online (Sandbox Code Playgroud)

GCC和Clang都对致电感到满意f,但对的致电并不满意g

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

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

g但是,在对的调用中,在推断的上下文T...另外出现的事实(这导致尝试尝试并失败)似乎导致g变得不可行。T...一旦尝试并失败,似乎没有退缩为空的迹象。

  • 这是故意行为吗?如果是这样,为什么?
  • 如果是这样,是否打算用“不以其他方式推论”的措辞来指明这种行为?(,这意味着仅当包未在任何推论上下文中出现时,才会发生空的回退)
  • 如果是这样,这个措词是否足够清楚?似乎“不以其他方式推论”的替代读法是“未进行推论,或试图进行推论而失败”。

dar*_*une 4

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

可以说,“未以其他方式推论”不是一个应该以某种方式或自动放松某些其他规则的子句,或者实际上它与它的格式错误的原因无关(与我认为您所暗示的相反)。

另一个非常简单的例子也许可以最好地证明这条规则:

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

int main() {
    f(int{42},short{42});
}
Run Code Online (Sandbox Code Playgroud)

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

另外,由于nullptr只是具有某种简单的类型- 它根本std::nullptr_t不适合参与模板参数推导。

因此,让我们暂时忘记非推导的上下文,并尝试使用推导的上下文:

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

int main() {
    S<> s1;
    g(&s1, nullptr);
}
Run Code Online (Sandbox Code Playgroud)

或者如果您愿意,只需

int main() {
    S<> s1;
    g(&s1, 0);
}
Run Code Online (Sandbox Code Playgroud)

两者都因同样的原因而失败。

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

对于您的情况,示例可能如下所示():

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

int main() {
    f(42);
    g(42, nullptr);
} 
Run Code Online (Sandbox Code Playgroud)

这是有效的。(请注意,如果您没有只需自己编写身份模板)

正如评论中所述,扭转问题也许会引出一个更有趣的问题?

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