在尝试使用可变参数模板时,参考了这个Stack Overflow的问题(注意:不必去那里查看问题以回答本问题),我发现以下模板重载函数在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;
}
代码: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>() | | | |
|---|-----------------------------------------------------------------------------|
最后两种情况(4和5)存在疑问:我对于“更专业的模板”的期望是否错误?如果是,那么在第4种情况中,clang或g ++谁是正确的?(请注意,代码在两者上都可以编译而不会出现任何错误或警告,但结果不同)。
为了回答这个问题,我多次查阅了规范中关于“更专业”的规则(14.5.6.2 Partial ordering of function templates)以及cppreference -- 看起来,“更专业”的规则应该给出我期望的结果(如果不是,则可能会出现歧义错误,但这也不是本例)。那么,我在这里漏掉了什么?
等待(1):请不要急于使用Herb Sutter的“prefer not to overload templates”以及他的template methods quiz。这些确实很重要,但语言仍然允许模板重载!(这确实是为什么你应该尽量不使用模板重载的一个加强点——在某些边缘情况下,它可能会混淆两个不同的编译器,或者混淆程序员。但问题不在于是否使用它,而在于:如果你使用它,正确的行为是什么?)。
等待(2):请不要急于提出其他可能的解决方案。肯定有其他解决方案。以下是其中两种:带有内部结构体的一种和另一种带有内部静态方法的解决方案。两种都是适当的解决方案,都能按预期工作,但上述模板重载行为的问题仍然存在。
PACK_A<Ts1..., Ts2...>
。编译器不应该能够推断出第一个参数包Ts1...
,因为它不在模板的末尾。因此,它应该为空,因此这个重载应该永远不会被选择。我不知道为什么 clang 在第四种情况下选择了它。 - W.F.template <typename... Ts1, typename... Ts2> constexpr bool starts_with(pack<Ts1..., Ts2...>, pack<Ts1...>)
可以解决这个问题。 - Richard HodgesTs...
并没有被推导出来,而是在模板化结构体被特化时就已知。这是最关键的区别。由于Ts...
在那里没有被推导,因此它可以被放置在那里,就像原样一样。 - W.F.