最近我报告了一个涉及函数参数包的msvc错误。此外,正如这里所证明的,msvc实际上是符合标准的。
然后,当我将示例修改为如下所示时,我注意到修改后的代码也不能在msvc中编译,但可以在clang和gcc中编译。代码如下:演示链接
template<typename T> struct C{};
template<typename T> void f(C<T>)
{
}
template<typename... T> void f(C<T...>)
{
}
int main()
{
f(C<int>{}); //Should this call succeed?
}
注意,在上面的例子中,我们将C<T...>
作为函数参数,而不仅仅是T...
。现在,在上面显示的示例中,我不能100%确定这是msvc问题还是标准不允许该程序。
所以我的问题是,上面显示的代码示例格式是否正确。也就是说,调用f(C<int>{});
是否应该成功地选择第一个过载void f(C<T>)
而不是void f(C<T...>)
?
换句话说,此处哪个编译器是正确的。
所以我的问题是,上面显示的代码示例格式是否正确。
代码的格式很好,因为根据偏序规则,具有非变参数模板参数的函数模板重载比具有变参数模板的重载更专业。
MSVC拒绝它是不正确的。
[temp.func.order]/1到/4告诉我们需要转向偏序
/1如果函数模板过载,则函数模板的使用specialization可能不明确[…]。重载函数模板声明的部分排序在以下上下文中用于选择函数模板specialization所指的函数模板:
- /1.1在函数模板专用化调用的重载解析过程中([over.match.best])
- […]
/2偏序通过依次转换每个模板(见下一段)并使用函数类型执行模板参数推导来选择两个函数模板中哪一个比另一个更专业。[…]如果是,则更专业的模板是通过部分排序过程选择的模板。如果两个推导都成功,则部分排序将选择更受约束的模板(如果存在),如下所示。
/3为每个类型、非类型或模板模板参数(包括模板参数包合成一个唯一的类型、值或类模板并将其替换为该参数的每次出现在模板的函数类型中。
/4使用转换后的函数模板的函数类型,对另一个模板执行类型推导,如[temp.dexecute.partial]中所述。
[暂时扣除部分]
/2使用两组类型来确定偏序。对于所涉及的每个模板,都有原始的函数类型以及变换后的函数类型。推导过程使用转换后的类型作为参数模板其他模板作为参数模板。[…]
/4上面从参数模板中指定的每个类型和参数模板中的相应类型都用作P和A.
/8使用生成的类型p和A,然后按照[temp.dexpract.type]中的描述进行推导。[…]如果对给定类型推导成功,则认为参数模板中的类型至少与参数模板的类型一样专业。
对于原始的单函数参数函数模板,会产生以下p/A对:
P A
-------- ----------
#1: C<T> C<Unique1...>
#2: C<T...> C<Unique2>
通常,当不在偏序的上下文中时,这两个对的推导都会成功。然而,当扣除作为部分订购的一部分执行时,[临时扣除类型]/9有一个特殊情况:
/9[…]在部分订购期间,如果Ai最初是一个包扩展:
- /9.2否则,如果Pi不是包扩展,则模板参数推导失败
此子句意味着以上#1
(C<T>
从C<Unique1...>
)的推导失败,而#2
(C<T...>
从C<Unique2>
)成功,并且C<Unique2>
被认为至少与C<T...>
一样专业。根据[temp.dexer.partial]/10,非变量函数模板重载因此至少与变量模板函数重载一样专业,并且在没有反之关系的情况下,更多专业化:
/10如果对于用于确定排序的每对类型,来自F的类型至少和来自G.的类型一样特殊如果F至少和G一样专业,并且G是至少没有F.那么专业
这让我们回到[temp.func.order]/2,通过偏序选择更专业的函数模板:
/2[…]推导过程确定其中一个模板是否比另一个更专业。如果是这样,则更专业的模板是部分排序过程选择的模板。
这也包含在[over.match.best]/2.5 中
/2给定这些定义,如果对于所有参数,可行函数F1被定义为比另一个可行函数F2更好的函数i、 ICSi(F1)不是比ICSi(F2)更差的转化序列,然后
- […]
- /2.5 F1和F2是函数模板专业化,F1的函数模板比F2的模板更专业根据中描述的偏序规则[temp.func.order],或者,如果不是,则为[…]