首页 > 解决方案 > 具有两个参数包的函数模板重载解析

问题描述

考虑以下代码:

#include<iostream>

template<class..., class... T>
int f(T...) { return 1; }

template<class... T>
int f(T...) { return 2; }

int main()
{
    std::cout << f(1);
}

它在 gcc 8.2 上编译和打印1,但由于调用f(1)不明确而无法在 clang 7 上编译。

如果调用被f()两个编译器替换,则编译失败,声称调用不明确。

如果参数包class... T被一个简单的参数class T(和T...)替换T,两个编译器也声称有歧义。

在第一个示例中,哪个编译器符合标准?我想这归结为函数模板的特定部分排序规则,或者以这种方式使用双参数包是否已经不正确?

编辑:

我的理解是双包本身不是格式错误的,因为如果第二个包可以从函数参数中推导出来,我阅读中的 [temp.param] 17.1/15 似乎明确允许这样做,这似乎是因为T...函数参数包。

也可以显式指定第一个参数包的参数,但不是第二个参数包,因此并不总是(在模板参数推导之后)至少一个参数包为空。我不确定这是否会使程序格式错误,因为我不知道在这种情况下如何阅读例如 [temp.res] 17.7/8.3。

gcc 和 clang 似乎都可以使用双参数包本身,例如,当删除第二个函数模板重载时,编译器都会 print 1。但这可能是格式不正确,不需要诊断的情况。

此外,我假设通过类模板参数推导,可变参数类模板可以定义可变参数构造函数模板,这意味着类似于我的双参数包示例的构造函数候选者,据我所知,相同的重载决议和模板参数推导需要放在那个语境下。这个问题是由另一个具有这种设置的问题引起的:Variadic class template deduction failed with gcc 8.2, compiles with clang and msvc 另请参阅:Deduction guides and variadic class templates with variadic template constructors - mismatched argument pack lengths

现在,我还找到了Deduction guide and variadic templates这个问题的答案,我认为这意味着 gcc 是错误的,并且该调用应该被认为是模棱两可的,但我想验证它是否同样适用于此。我也欢迎更详细的推理,因为函数模板部分排序规则对我来说似乎很不清楚。

标签: c++language-lawyeroverload-resolutiontemplate-argument-deduction

解决方案


这里有两个问题。


首先,[temp.deduct.partial]/12(我也引用了这个例子,因为它与你的相似)说:

在大多数情况下,如果并非所有模板参数都有值,则推导失败,但出于偏序目的,模板参数可能会保持没有值,前提是它未用于用于偏序的类型中。[ 注意:在非推导上下文中使用的模板参数被视为已使用。— 尾注] [ 示例:

template <class T> T f(int);            // #1
template <class T, class U> T f(U);     // #2
void g() {
  f<int>(1);                            // calls #1
}

—结束示例]

用于部分排序的类型T...根据[temp.deduct.partial]/3

用于确定排序的类型取决于完成部分排序的上下文:

  • 在函数调用的上下文中,使用的类型是函数调用具有参数的那些函数参数类型。

  • ...

所以第一个未命名的模板参数包class...不影响偏序的结果。由于两个函数模板没有其他区别,因此两者都没有比另一个更专业,从而导致模棱两可的调用。

可能与 GCC 的bug 49505有关。


其次,即使第二个函数模板不存在,调用仍然应该是格式错误的。根据[temp.arg.explicit]/3

...一个尾随模板参数包没有被推导出来,将被推导出为一个空的模板参数序列......

只有尾随模板参数包可以推导出为空包,而第一个未命名的模板参数包class...不是尾随模板参数包。

GCC ( bug 69623 ) 和 Clang ( bug 26435 ) 都有针对此问题的错误。


推荐阅读