[英]Specialized template function with deleted “general” case fails to compile with g++ <=4.8.0 and clang++
[英]Which is the more specialized template function? clang and g++ differ on that
在使用可变参数模板时,遵循这个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;
}
代码: 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功能模板的部分排序)和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>())
仍然是示例5.不满足此要求,并且不允许编译器选择过载。
仅供参考:不是答案。 这是对评论中的问题的回答:
在gcc5.3上进行以下小改动会导致它产生预期的结果,或至少与clang相同的结果。
rhodges@dingbat:~$ cat nod.cpp
#include <iostream>
using namespace std;
template <class... Ts>
struct pack { };
template <class a, class b>
constexpr bool starts_with(a, b) {
return false;
}
template <typename... Ts1, typename... Ts2 >
constexpr bool starts_with(pack<Ts1..., Ts2...>, pack<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;
}
rhodges@dingbat:~$ g++ -std=c++14 nod.cpp && ./a.out
false
false
false
true
false
rhodges@dingbat:~$ g++ --version
g++ (Ubuntu 5.3.1-14ubuntu2.1) 5.3.1 20160413
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
rhodges@dingbat:~$
并且为了记录,修改程序以评估推断的上下文中的所有包在两个平台上都取得了成功:
rhodges@dingbat:~$ cat nod.cpp
#include <iostream>
using namespace std;
template <class... Ts>
struct pack { };
template <class a, class b>
constexpr bool starts_with_impl(a, b) {
return false;
}
template<typename...LRest>
constexpr bool starts_with_impl(pack<LRest...>, pack<>)
{
return true;
}
template<typename First, typename...LRest, typename...RRest>
constexpr bool starts_with_impl(pack<First, LRest...>, pack<First, RRest...>)
{
return starts_with_impl(pack<LRest...>(), pack<RRest...>());
}
template <typename... Ts1, typename... Ts2 >
constexpr bool starts_with(pack<Ts2...> p1, pack<Ts1...> p2) {
return starts_with_impl(p1, p2);
}
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;
}
rhodges@dingbat:~$ g++ -std=c++14 nod.cpp && ./a.out
false
false
false
true
true
感谢WF指导我这个方向。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.