哪个是更专业的模板功能?clang和g ++有所不同

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):请不要急于带来其他可能的解决方案.肯定有.这里有两个:一个是内部结构,另一个是内部静态方法.两者都是合适的解决方案,都按预期工作,但有关上述模板重载行为的问题仍然存在.

W.F*_*.F. 4

正如 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. 仍然不满足此要求,并且不允许编译器选择重载。