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

g++ and clang++ different behaviour with integral template parameter

本文关键字:参数 clang++ g++      更新时间:2023-10-16

我有以下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>具有值为0int文字变元,其正被转换为模板参数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 longlong 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。