g ++和clang ++与积分模板参数的不同行为

我有以下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臭虫,深埋在标准中。 问题是,在几乎所有情况下,非types模板参数都可以转换为模板参数的types。 例如,expression式Int<0>具有值为0int立即数参数,其被转换为模板参数N的typesunsigned long long

14.8.2模板参数推导[temp.deduct] / 2第2项

– 非types参数必须与相应的非types模板参数的types匹配, 或者必须可以转换为 14.3.2中指定的相应非types参数的types,否则types扣除失败。

由于你的类模板is_int<T>有部分专业化,我们需要看看

14.5.5.1类模板部分特殊化的匹配[temp.class.spec.match]

1当在需要实例化类的上下文中使用类模板时,有必要确定是使用主模板还是使用部分特化之一来生成实例化。 这是通过将类模板特化的模板参数与部分特化的模板参数列表匹配来完成的

2如果部分特化的模板参数可以从实际的模板参数列表(14.8.2) 推导出来,则部分特化与给定的实际模板参数列表相匹配。

所以看起来我们可以继续前面14.8.2 / 2的第二个子弹,并且匹配第二个特殊化(尽pipe在这种情况下会有更复杂的重载决策游戏)。

决议

然而,事实certificate(正如@DyP在评论中提到的那样)标准中的另一个条款取代了这个:

14.8.2.5从types[temp.deduct.type]中推导模板参数

17如果在函数模板的非types模板参数的声明中,在函数参数列表的expression式中使用非types模板参数,并且如果推导出相应的模板参数, 则模板参数types必须与模板参数的types完全匹配 ,除了从数组绑定中推导出来的模板参数可以是任何整型。 [例如:

 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的局部is_int不能被推导出来,因为它并不是完全相同的types( unsigned long long vs long long )作为Int类模板的正式非types模板参数。

您可以通过在is_int的部分专用化is_int非types模板参数N赋予与主模板Int的非types参数N相同的types来解决此问题。

 template <IntType N> // ^^^^^^^^ struct is_int<Int<N>> : std::true_type {}; 

现场示例

铿锵是不一致的。 由于它接受你的代码 ,我期待下面的代码必须输出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>匹配?

我的结论是:

  • 如果铿锵在我的情况是正确的,那么你的情况是不正确
  • 如果铿锵在你的情况是正确的,那么对来说这是不正确的。

无论哪种方式,铿锵似乎有错误。

另一方面,GCC至less是一致的。 这并不能certificate它没有错误 – 这可能意味着它在两种情况下都有错误! 除非有人提出这个标准,并且显示它也有bug,否则我会在这种情况下信任GCC。