【发布时间】:2017-04-14 23:50:53
【问题描述】:
在尝试依赖可变参数模板实现一些事情时,我偶然发现了一些我无法解释的东西。我将问题归结为以下代码sn-p:
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 认为第一个专业化“更专业化”(通过部分排序:请参阅下一节),因为它编译良好并打印:
我比可变参数更专业,呵呵!
live demo 可以在 Coliru 上找到。我也尝试使用 gcc 的 HEAD 版本,得到了同样的错误。
我的问题是:由于这两个著名的编译器的行为不同,哪一个是正确的,这段代码是正确的 C++ 吗?
标准解释 (C++14 current draft)
根据 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);
}
在 clang 和 gcc 中都会产生编译错误 [模糊调用]:live demo。我预计 clang 至少会具有与类模板案例一致的行为。
然后,这是我对标准的解释(我似乎与@Danh 分享),所以此时我们需要language-lawyer 来确认这一点。
注意:我浏览了一点 LLVM 的错误跟踪器,但找不到在这个问题中观察到的函数模板重载行为的票证。
【问题讨论】:
标签: language-lawyer c++ c++11 language-lawyer variadic-templates template-specialization