【问题标题】:clang does not infer template argument in variadic template function with varargsclang 不使用可变参数推断可变参数模板函数中的模板参数
【发布时间】:2016-10-05 08:21:18
【问题描述】:

考虑代码:

#include <tuple>

template<class... Args, class T>
T method(std::tuple<Args...>, Args..., T, ...) {
   return T();
}

int main() {
    method(std::make_tuple<int, float, double>(1, 1.0f, 1.0),
        1, 1.0f, 1.0, 1);
}

g++ 从 4.9 开始编译没有问题。另一方面,clang++ 提供了一个错误:

main.cpp:9:5: error: no matching function for call to 'method'
    method(std::make_tuple<int, float, double>(1, 1.0f, 1.0),
    ^~~~~~
main.cpp:4:3: note: candidate template ignored: couldn't infer template argument 'T'
T method(std::tuple<Args...>, Args..., T, ...) {
  ^
1 error generated.

哪个编译器是正确的?

【问题讨论】:

  • 据我了解,它是这样工作的:顶层的Args...不在参数列表的末尾,所以它出现在非推导上下文中,这使得Args...在模式匹配参数推导期间相当于一个 0 大小的参数子列表。因此,随后的T 匹配出现在元组参数之后的1,并将推导出为int。稍后当编译器将参数int,float,double 替换为Args... 时,编译参数列表为tuple&lt;...&gt;,int,float,double,int。此列表需要匹配参数类型..
  • 它在这种情况下是偶然的,因为最后一个 1 的类型与第一个 1 的类型相同(实际上 GCC 似乎并不关心......它编译如果最后一个1 是1.0.. 但有趣的是T 保持int)。当三种类型的包扩展为Args... 时,可能clang 放弃了将推断为int 的T 从其先前的推断参数位置移开,并报告它无法推断出T。跨度>
  • 如果你删除了...,gcc 也不会编译它。
  • @W.F.实际上,Clang 也拒绝符合标准的测试代码,因此 Clang 在任何情况下都有一些未实现的功能(请参阅coliru.stacked-crooked.com/a/4350348b8080a730)。所以对于clang来说,原因是它还没有实现1388。对于 GCC,我不知道根据规范应该发生什么。直觉认为它应该失败,因为无法合理推断出T。
  • 啊,我们在open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1399 有一个像你这样的案例。本质上,1388 的“从未演绎”意味着它在其他可能可演绎的上下文中也没有被演绎。因此,在您的tuple&lt;Args...&gt; 中,它也不会为Args 提供类型列表,因此最终结果是,您的代码格式不正确,因为Args 将保持为空并且与提供的tuple&lt;&gt; 不匹配论据。

标签: c++ g++ c++14 variadic-templates clang++


【解决方案1】:

代码格式不正确。两个编译器都正确编译失败:

#include <tuple>

template<class... Args, class T>
T method(std::tuple<Args...>, Args..., T) {
   return T();
}

int main() {
    method(std::make_tuple<int, float, double>(1, 1.0f, 1.0),
        1, 1.0f, 1.0, 1);
}

我们确实有,来自 [temp.deduct.call],强调我的:

当一个函数参数包 出现在非推导上下文 (14.8.2.5) 中,该参数包的类型从不推导。

Args... 出现在非推导上下文中,因为来自 [temp.deduct.type]:

非推导上下文是:[...] 一个函数参数包,不会出现在 parameter-declaration-list 的末尾。

因此,按照标准的确切措辞,Args... 不应被推导出来,因此您必须明确提供它 - 在这种情况下,由于T 仍然不可能。

但在这种情况下,Args... 同时出现在推导的 和 非推导上下文中 - 应该没有什么可以阻止 Args... 从 tuple 参数推导出为 {int, float, double}然后简单地将推导重新用于可变参数,然后在最后将T 推导为int。

但是这个提议的过程会与我们独立处理每个参数/参数对的典型演绎过程相冲突。在这种情况下,对T 的推导很大程度上取决于从tuple&lt;&gt; 参数/参数对中对Args... 的推导。

如果你只是翻转排序:

template <class... Args, class T>
T method(std::tuple<Args...>, T, Args...) { ... }

然后两个编译器都会编译它。

我不知道为什么 gcc 接受带有可变参数的原始示例。这显然是错误的。

另外,如果你翻转模板参数规范,这样你就可以指定T而不需要扣除,那么clang接受但gcc拒绝:

template<class T, class... Args>
T method(std::tuple<Args...>, Args..., T) {
    return T();
}

int main() {
    method<int>(std::make_tuple<int>(1), 1, 1);
}

我希望这是格式正确的 - 可以推断出 Args... 而 T 不必如此。因此,在我看来,“从不推断”的措辞是有问题的。

【讨论】:

  • 这是一个不那么精致的答案,更多的意识流漫无边际地接近一个 - 但希望它背后的想法是有意义的。
  • 翻转排序没问题,因为函数参数列表中间不再有模板参数包。因此,“从未推断”不再适用,因为它不再出现在非推断上下文中。
  • @Johannes woops,这意味着附加到另一个示例。第二个应该工作,不是吗?
  • 我认为拒绝最初询问者代码的理由相同:规范文本 (DR 1399) 说“Args”是“从不推断”。它不仅是 ", Args..., " 的非推导上下文,而且它使 Args 在任何地方都完全不可推导。至少我是这么读的,这对于 DR1399 的例子来说是有意义的。它还说“永远不会推导出参数包”,而不是“函数参数包”,因此我认为它适用于“类 ...Args”,而不仅仅是“Args ...”。
  • @Johannes 但是那里的例子没有一个参数包可以一起推导和非推导的情况,我认为没有考虑这种(过于复杂)的情况。
猜你喜欢
  • 2013-09-14
  • 2015-08-18
  • 2016-12-01
  • 2021-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多