g ++和clang ++使用整数模板参数的不同行为

时间:2013-11-28 14:00:56

标签: c++ templates c++11 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, "");
     ^
$ 

问题是由不同的整数模板参数引起的。 在这种情况下哪个编译器是正确的?

2 个答案:

答案 0 :(得分:25)

惊喜

这是一个微妙的Clang bug,深深埋藏在标准中。问题在于,几乎在所有情况下,非类型模板参数都可以转换为模板参数的类型。例如。表达式Int<0>具有值为int的{​​{1}}字面值参数,该参数将转换为模板参数0的{​​{1}}类型。

14.8.2模板参数演绎[temp.deduct] / 2 2nd bullet

  

- 非类型参数必须与相应的非类型的类型匹配   模板参数,或必须可转换为的类型   相应的非类型参数,如14.3.2中规定的,否则   类型扣除失败。

由于您的班级模板unsigned long long具有部分专业化,我们需要查看

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

  

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

     

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

所以看起来我们可以继续使用早期的14.8.2 / 2第2个子弹的引用并匹配第二个特殊化(尽管在这种情况下,必须进行更复杂的重载分辨率游戏)。

分辨率

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

14.8.2.5从类型[temp.deduct.type]中扣除模板参数

  

17如果,在声明具有非类型的函数模板中   template-parameter,非类型模板参数用于   函数参数列表中的表达式,如果相应的话   推导出template-argument,模板参数类型必须匹配   模板参数的类型完全,除了a   从数组绑定推导出的模板参数可以是任何积分   类型。 [例如:

N

结果是is_int<T>的部分特化无法推断,因为它不像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 vs unsigned long long)形式非类型模板参数。

您可以通过在long long的部分特化中为非主要模板中的非类型参数Int提供非类型模板参数N来解决此问题。 1}}。

is_int

Live Example

答案 1 :(得分:10)

Clang不一致。 Since it accepts your code,我希望以下代码必须输出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>{});
}

但令人惊讶的是,它输出了这个(online demo):

f(T)

这表明Int<0>与接受参数Int<N>的第二个重载不匹配。如果是这样,那么当它被用作类模板的模板参数(在你的情况下)时,为什么它与Int<N>匹配?

我的结论:

  • 如果我的情况下Clang是正确的,那么在你的情况下它是错误的。
  • 如果Clang在你的情况下是正确的,那么在我的情况下它是不正确的。

无论哪种方式,Clang似乎都有bug。

另一方面,海湾合作委员会至少是一致的。这并不能证明它没有bug - 这可能意味着它在两种情况下都有bug!除非有人提出标准并且显示它还有 错误,否则在这种情况下我会信任GCC。