g++和clang++在积分模板参数下的不同行为
g++ and clang++ different behaviour with integral template parameter
我有以下C++11代码。
#include <type_traits>
using IntType = unsigned long long;
template <IntType N> struct Int {};
template <class T>
struct is_int : std::false_type {};
template <long long N>
struct is_int<Int<N>> : std::true_type {};
int main()
{
static_assert (is_int<Int<0>>::value, "");
return 0;
}
Clang++3.3编译代码,但在g++4.8.2上静态断言失败
$ g++ -std=c++11 main.cpp
main.cpp: In function ‘int main()’:
main.cpp:15:5: error: static assertion failed:
static_assert (is_int<Int<0>>::value, "");
^
$
该问题是由不同的积分模板参数引起的。在这种情况下,哪个编译器是正确的?
惊喜
这是一个微妙的Clang bug,深深地埋在标准中。问题是,在几乎所有情况下,非类型的模板参数都可以转换为模板parameter的类型。例如,表达式Int<0>
具有值为0
的int
文字变元,其正被转换为模板参数N
的类型unsigned long long
。
14.8.2模板参数推导[临时推导]/2第二个项目符号
--非类型参数必须与相应非类型的类型匹配模板参数,或必须可转换为14.3.2中规定的相应非类型参数,否则类型推导失败。
由于您的类模板is_int<T>
具有部分专业化,我们需要查看
14.5.5.1类模板部分专业化的匹配[temp.class.spec.match]
1当类模板用于需要类的实例化,有必要确定实例化将使用主模板或部分专业化这是通过匹配模板来完成的类模板的参数使用模板专门化部分专业化的参数列表。
2部分专业化与给定的实际模板参数匹配列出部分专业化的模板参数是否可以根据实际模板参数列表(14.8.2)推导。
因此,我们似乎可以继续前面引用的14.8.2/2第二个子弹,并匹配第二个专业化(尽管在这种情况下,必须玩一个更复杂的过载解决游戏)。
决议
然而,事实证明(正如@DyP在评论中所提到的),本标准中的另一条条款取代了以下条款:
14.8.2.5从类型[temp.dexecut.type]中推导模板参数
17如果,在具有非类型的函数模板的声明中template参数,在函数参数列表中的表达式,如果模板参数被推导,模板参数类型应匹配模板参数的类型恰好,除了从数组边界推导出的模板自变量可以是任何积分类型[示例:
template<int i> class A { / ... / };
template<short s> void f(A<s>);
void k1() {
A<1> a;
f(a); // error: deduction fails for conversion from int to short
f<1>(a); // OK
}
结果是,is_int
的部分专门化不能推导出来,因为它不采用与Int
类模板的形式非类型模板参数完全相同的类型(unsigned long long
与long long
)。
您可以通过将is_int
的部分专用化中的非类型模板参数N
的类型与主模板Int
中的非型参数N
的类型相同来解决此问题。
template <IntType N>
// ^^^^^^^^
struct is_int<Int<N>> : std::true_type {};
实例。
Clang不一致。由于它接受您的代码,我希望以下代码必须输出f(Int<long long>)
而不是f(T)
:
using IntType = unsigned long long;
template <IntType N> struct Int {};
template<typename T>
void f(T) { std::cout << "f(T)" << std::endl; }
template<long long N>
void f(Int<N>) { std::cout << "f(Int<long long>)" << std::endl; }
int main()
{
f(Int<0>{});
}
但令人惊讶的是,它输出了这个(在线演示):
f(T)
这表明Int<0>
与接受参数为Int<N>
的第二个重载不匹配。如果是这样,那么当它被用作类模板的模板参数时(在您的情况下),为什么它与Int<N>
匹配?
我的结论:
- 如果Clang在我的情况下是正确的,那么它在你的
- 如果Clang在您的情况下是正确的,那么它在my的
不管怎样,Clang似乎有窃听器。
另一方面,GCC至少是一致的。但这并不能证明它没有缺陷;这可能意味着它在这两种情况下都有bug!除非有人提出标准语言并显示它也有错误,否则在这种情况下,我会信任GCC。
- Clang bug?使用指针作为模板参数
- 类模板参数推导-clang和gcc不同
- 如何修复带有 clang 的参数'args'缺少默认参数的问题?
- GCC/CLang不同意模板模板参数的部分特化
- Clang-Format 不能正确分配函数参数
- constexpr 函数的常量引用参数:gcc/msvc vs clang/icc
- python.clang AST 解析:获取字段声明的 c++ 模板参数
- 如何在 Android/NDK 上将命令行参数从 gradlew.bat 传递到 Clang
- 我可以用clang AST从模板专业化中获得默认的模板参数吗
- 模板模板参数导致Clang下的编译器错误,而不是GCC
- 是否有 g++ 或 clang++ 调试选项来指导可变参数模板化
- clang 格式堆叠所有 if 语句参数,如果它们太长
- 带有 gcc 和 clang 的可变参数宏扩展的奇怪行为
- clang 格式:始终断开所有参数,每行一个
- 修改嵌套 lambda 中捕获的参数:gcc 与 clang?
- 使用函数参数作为常量表达式的一部分 - gcc vs clang
- 当超出列时,clang格式强制每个参数/参数拥有自己的行?
- 自动非型模板参数:clang中的偏见偏专长
- 使用 constexpr 函数的结果作为模板参数(clang vs gcc)
- 重新解释一个模板非类型参数:clang c++14 vs c++1z