Ami*_*rsh 18 c++ templates overloading variadic-templates c++11
在使用可变参数模板时,遵循这个SO问题(注意:并不是强制要求遵循这个问题),对于以下模板重载函数,我得到了clang(3.8)和g ++(6.1)的不同行为:
template <class... Ts>
struct pack { };
template <class a, class b>
constexpr bool starts_with(a, b) {
return false;
}
template <template <typename...> class PACK_A,
template <typename...> class PACK_B, typename... Ts1, typename... Ts2>
constexpr bool starts_with(PACK_A<Ts1..., Ts2...>, PACK_B<Ts1...>) {
return true;
}
int main() {
std::cout << std::boolalpha;
std::cout << starts_with(pack<int, float, double>(),
pack<float, int, double>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(),
pack<int, float, double, int>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(),
pack<int, float, int>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(),
pack<int, float, double>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(),
pack<int>()) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
代码:http://coliru.stacked-crooked.com/a/b62fa93ea88fa25b
|---|-----------------------------------------------------------------------------|
| # |starts_with(a, b) | expected | clang (3.8) | g++ (6.1) |
|---|-----------------------------------|-------------|-------------|-------------|
| 1 |a: pack<int, float, double>() | false | false | false |
| |b: pack<float, int, double>() | | | |
|---|-----------------------------------|-------------|-------------|--------- ---|
| 2 |a: pack<int, float, double>() | false | false | false |
| |b: pack<int, float, double, int>() | | | |
|---|-----------------------------------|-------------|-------------|--------- ---|
| 3 |a: pack<int, float, double>() | false | false | false |
| |b: pack<int, float, int>() | | | |
|---|-----------------------------------|-------------|-------------|--------- ---|
| 4 |a: pack<int, float, double>() | true | true | false |
| |b: pack<int, float, double>() | | | |
|---|-----------------------------------|-------------|-------------|--------- ---|
| 5 |a: pack<int, float, double>() | true | false | false |
| |b: pack<int>() | | | |
|---|-----------------------------------------------------------------------------|
Run Code Online (Sandbox Code Playgroud)
最后两个案例(4和5)存在问题:我对更专业模板的期望是错误的吗?如果是这样,谁是正确的4,clang或g ++?(请注意,代码编译时没有任何错误或警告,但结果不同).
试图自己回答这个问题,我多次通过规范中的"更专业"规则(14.5.6.2功能模板的部分排序)和cppreference - 似乎更专业的规则应该给出我期待的结果(如果没有,可能会出现歧义错误,但事实并非如此).那么,我在这里错过了什么?
等等(1):请不要急于带上Herb Sutter 的" 不要超载模板 "和他的模板方法测验.这些肯定很重要,但语言仍然允许模板重载!(这确实是一个强化点,为什么你不应该重载模板 - 在某些边缘情况下,它可能会混淆两个不同的编译器,或者使程序员感到困惑.但问题不在于是否使用它,它是:什么是如果你使用它,这是正确的行为吗?).
等(2):请不要急于带来其他可能的解决方案.肯定有.这里有两个:一个是内部结构,另一个是内部静态方法.两者都是合适的解决方案,都按预期工作,但有关上述模板重载行为的问题仍然存在.
正如 Holt 提到的,在可变参数模板参数推导方面,标准非常严格:
14.8.2.5/9
如果 P 具有包含 T 或 i 的形式,则将相应模板参数列表 P 的每个参数 Pi 与 A 的相应模板参数列表的相应参数 Ai 进行比较。如果 P 的模板参数列表包含包扩展,不是最后一个模板参数,整个模板参数列表是非推导的上下文。如果 Pi 是包扩展,则将 Pi 的模式与 A 的模板参数列表中的每个剩余参数进行比较。每次比较都会推导出 Pi 扩展的模板参数包中后续位置的模板参数。
按照 TC 的解释,这意味着Ts1...可以从第二个参数中推导出来,但没有留下Ts2...推论的空间。因此,显然 clang 在这里是正确的,而 gcc 是错误的...只有当第二个参数包含完全相同的模板参数时才应选择重载,例如:
starts_with(pack<int, float, double>(), pack<int, float, double>())
Run Code Online (Sandbox Code Playgroud)
示例 5. 仍然不满足此要求,并且不允许编译器选择重载。