带有两个参数包的函数模板重载解析

12

考虑以下代码:

#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。两个编译器似乎都对双重参数包本身没有问题,例如当删除第二个函数模板重载时,两个编译器都打印1。但这可能是一种不需要诊断的非法情况。此外,我认为,通过类模板参数推导,可变类模板可以定义具有可变模板构造函数的构造函数候选项,该构造函数候选项类似于我的双参数包示例,并且就我所知,在该上下文中进行相同的重载分辨率和模板参数推导。这个问题是由另一个具有这样设置的问题引起的:Variadic class template deduction fails 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是错误的,应该认为调用是模棱两可的,但我想要验证它是否适用于这里以同样的方式。我也希望更详细地解释一下推理,因为函数模板部分排序规则对我来说似乎非常不清楚。

1
第一个 f 看起来对我来说不太正确。编译器如何确定地推断出这两个包? - Richard Hodges
@RichardHodges - {}{int}(或者其他作为函数参数传递的内容)。一个空包可以被推断出来...我想。 - StoryTeller - Unslander Monica
@RichardHodges 我添加了一些说明,为什么我觉得双重打包本身并不明显是非法的。 - user10605163
1个回答

5
这里有两个问题。
首先,[temp.deduct.partial]/12 (我引用了示例,因为它与您的类似) 指出:

[temp.deduct.partial]/12(以下是引用):

In most cases, deduction fails if not all template parameters have values, but for partial ordering purposes a template parameter may remain without a value provided it is not used in the types being used for partial ordering. [ Note: A template parameter used in a non-deduced context is considered used. — end note ] [ Example:

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

— end example ]

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

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

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

  • ...

因此,第一个未命名的模板参数包class...不会影响部分排序的结果。由于两个函数模板没有其他差异,因此它们都不比另一个更加专业化,导致调用不明确。

这可能与GCC的错误49505有关。


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

...否则无法推导出的尾随模板参数包将被推导为空模板参数序列...

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

GCC (错误69623)和Clang (错误26435)都存在此问题的错误。


@Oktalist 这是一个不同的问题。这个问题已经通过一次编辑变更得到解决。 - xskxzr
对于第二点,您从temp.arg.explicit中引用的是一个注释,因此不是规范性的;它并不能证明非尾随包将不会被推导为空包。事实上,http://eel.is/c++draft/temp#param-example-7就足以证明相反的情况,因为这意味着如果`U`可以被推导(来自尾随包),那么`template<class... T, class... U> void f`将是有效的。 - ecatmur
如果像 f<int>(4.2) 这样调用,则 template<class... T, class... U> void f(U...) 将是有效的,即 T 被明确指定,但是 OP 的代码不是这种情况。尽管我阅读了P1787R4,但“注”仍然是在之后添加的,我仍然不理解为什么要添加它... - xskxzr
我也是这样认为。你可能希望使用模板参数来调用函数f,但是没有主要的编译器会像那样做(所有 4 个编译器的行为都相同),因此这很可能是标准应该改变以匹配实现的领域。相关:CWG2055(正在起草)http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2055 - ecatmur

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接