在尝试依赖可变参数模板实现一些东西时,我偶然发现了一些我无法解释的东西。我将问题归结为以下代码片段:
template <typename ... Args>
struct A {};
template <template <typename...> class Z, typename T>
struct test;
template <template <typename...> class Z, typename T>
struct test<Z, Z<T>> {
static void foo() {
std::cout << "I'm more specialized than the variadic spec, hehe!" << std::endl;
}
};
template <template <typename...> class Z, typename T, typename ... Args>
struct test<Z, Z<T, Args...>> {
static void foo() {
std::cout << "I'm variadic!" << std::endl;
}
};
int main() {
test<A, A<int>>::foo();
}
在 gcc 下,它会产生错误,因为它在尝试实例化时认为两个专业化是同等专业化的test<A, A<int>>
:
main.cpp: In function 'int main()':
main.cpp:25:24: error: ambiguous template instantiation for 'struct test<A, A<int> >'
test<A, A<int>>::foo();
^~
main.cpp:11:12: note: candidates are: template<template<class ...> class Z, class T> struct test<Z, Z<T> > [with Z = A; T = int]
struct test<Z, Z<T>> {
^~~~~~~~~~~~~
main.cpp:18:12: note: template<template<class ...> class Z, class T, class ... Args> struct test<Z, Z<T, Args ...> > [with Z = A; T = int; Args = {}]
struct test<Z, Z<T, Args...>> {
然而,clang 认为第一个专业化“更专业”(通过部分排序:参见下一节),因为它编译良好并打印:
我比可变参数规范更专业,呵呵!
A live demo http://coliru.stacked-crooked.com/a/f0c1f71658fac3a3 can be found on Coliru. I also tried using gcc's HEAD version and got the same errors.
我的问题是:由于这两个著名的编译器的行为不同,哪一个是正确的,这段代码是正确的 C++ 吗?
标准解释(C++14 当前草案 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf)
从 C++14 标准草案的第 §14.5.5.1 和 $14.5.5.2 节中,触发部分排序以确定应选择哪个专业化:
(1.2) — 如果找到多个匹配的专业化,则使用偏序规则 (14.5.5.2) 来确定
其中一个专业是否比其他专业更专业。如果没有一个专业
比所有其他匹配的专业化更专业,那么类模板的使用是
模棱两可,程序格式不正确。
现在根据§14.5.5.2,类模板特化通过以下过程转换为函数模板:
对于两个类模板部分特化,第一个比第二个更特化,因为
重写两个函数模板后,第一个函数模板比第二个更专业
根据函数模板的排序规则(14.5.6.2):
(1.1) — 第一个函数模板与第一个部分特化具有相同的模板参数,并且具有
单个函数参数,其类型是带有模板参数的类模板特化
第一个部分专业化,以及
(1.2) — 第二个函数模板与第二个部分特化具有相同的模板参数
并且有一个函数参数,其类型是类模板特化与模板
第二部分特化的参数。
因此,我尝试使用上述转换应生成的函数模板重载来重现该问题:
template <typename T>
void foo(T const&) {
std::cout << "Generic template\n";
}
template <template <typename ...> class Z, typename T>
void foo(Z<T> const&) {
std::cout << "Z<T>: most specialized overload for foo\n";
}
template <template <typename ...> class Z, typename T, typename ... Args>
void foo(Z<T, Args...> const&) {
std::cout << "Z<T, Args...>: variadic overload\n";
}
现在尝试像这样使用它:
template <typename ... Args>
struct A {};
int main() {
A<int> a;
foo(a);
}
yields a compilation error [ambiguous call] in both clang and gcc: live demo http://coliru.stacked-crooked.com/a/8f4232a317c6f7be. I expected clang would at least have a behavior consistent with the class template case.
然后,这是我对标准的解释(我似乎与@Danh 分享),所以此时我们需要一个语言律师 /questions/tagged/language-lawyer来确认这一点。
Note:我浏览了一点 LLVM 的错误跟踪器,但找不到此问题中在函数模板重载上观察到的行为的票证。