Rer*_*ito 16 c++ template-specialization language-lawyer variadic-templates c++11
在尝试实现依赖于可变参数模板的一些事情时,我偶然发现了一些我无法解释的事情.我将问题归结为以下代码片段:
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();
}
Run Code Online (Sandbox Code Playgroud)
在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...>> {
Run Code Online (Sandbox Code Playgroud)
然而,clang认为第一个专业化"更专业化"(通过部分排序:参见下一节),因为它编译精细并打印:
我比变量规格更专业,呵呵!
A live demo可以在Coliru找到.我也试过使用gcc的HEAD版本并得到了同样的错误.
我的问题是:由于这两个着名的编译器行为不同,哪一个是正确的,这段代码是正确的C++?
从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";
}
Run Code Online (Sandbox Code Playgroud)
现在尝试使用它像这样:
template <typename ... Args>
struct A {};
int main() {
A<int> a;
foo(a);
}
Run Code Online (Sandbox Code Playgroud)
在clang和gcc中产生编译错误[模糊调用] : live demo. 我希望clang至少会有一个与类模板情况一致的行为.
然后,这是我对标准的解释(我似乎与@Danh分享),所以在这一点上我们需要语言律师来证实这一点.
注意:我浏览了一下LLVM的错误跟踪器,但在此问题中找不到功能模板重载时观察到的行为的票证.
对于两个类模板部分特化,第一个比第二个更专业如果,给定以下对两个函数模板的重写,根据函数模板的排序规则,第一个函数模板比第二个更专业( )
[temp.func.order]:
两个函数模板中的每一个都具有与相应的部分特化相同的模板参数。
每个函数模板都有一个函数参数,其类型是类模板特化,其中模板参数是部分特化的 simple-template-id 的 template-argument-list 中每个模板参数的函数模板中对应的模板参数。
的顺序:
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;
}
};
Run Code Online (Sandbox Code Playgroud)
取决于以下顺序:
template <template <typename...> class Z, typename T>
void bar(test<Z, Z<T>>); // #1
template <template <typename...> class Z, typename T, typename ... Args>
void bar(test<Z, Z<T, Args...>>); // #2
Run Code Online (Sandbox Code Playgroud)
部分排序通过依次转换每个模板(请参阅下一段)并使用函数类型执行模板参数推导,选择两个函数模板中哪一个比另一个更专业。推导过程确定其中一个模板是否比另一个更专业。如果是这样,则部分订购过程会选择更专业的模板。
为了生成转换后的模板,对于每个类型、非类型或模板模板参数(包括其模板参数包([temp.variadic]))分别合成一个唯一的类型、值或类模板,并将其替换为每次出现的模板的函数类型中的该参数。
使用转换后的函数模板的函数类型,针对其他模板执行类型推导,如 中所述
[temp.deduct.partial]。
通过这些段落,对于从任何合成模板Z0和类型转换而来的任何函数T0,可以形成#1,我们可以用 进行类型推导#2。但是用任何类型和任何非空集合的#2虚拟模板转换而来的函数都不能从 中推导出来。显然比Z2T2Args2#1#1#2。
clang++ 在这种情况下是正确的。
实际上,这个和这个在 g++ 和 clang 中都无法编译(因为不明确)。似乎两个编译器都很难处理模板模板参数。(后一个是明确排序的,因为它的顺序与没有函数调用的顺序相同)。