【问题标题】:g++ and clang++ different behaviour with integral template parameterg++ 和 clang++ 具有完整模板参数的不同行为
【发布时间】:2013-12-14 14:36:29
【问题描述】:

我有以下 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, "");
     ^
$ 

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

【问题讨论】:

  • 好问题,非常微妙。
  • 只是一个注释 - 在 VS2010 上编译(将 using 更改为 typedef
  • @jrok 不是,这个问题是关于 Clang 中的一个错误
  • @AlecTeal 哦,所以你知道这是 clang 的错误吗?我急切地等待着你的答复。 :)

标签: c++ templates c++11 g++ clang++


【解决方案1】:

惊喜

这是一个微妙的 Clang 错误,深埋在标准中。问题是在几乎所有情况下,非类型模板arguments都可以转换为模板parameter的类型。例如。表达式Int&lt;0&gt; 有一个值为0int 字面量参数,它被转换为模板参数Nunsigned long long 类型。

14.8.2 模板参数推导 [temp.deduct]/2 2nd bullet

-- 非类型参数必须匹配对应非类型的类型 模板参数,或必须可转换为 14.3.2 中指定的相应非类型参数,否则 类型推导失败。

由于你的类模板is_int&lt;T&gt;有部分特化,我们需要看看

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

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

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

所以看起来我们可以继续前面提到的 14.8.2/2 2nd bullet 并匹配第二个专业化(尽管在这种情况下必须玩更复杂的重载决议游戏)。

分辨率

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

14.8.2.5 从类型 [temp.deduct.type] 推导出模板参数

17 如果,在一个非类型的函数模板的声明中 模板参数,非类型模板参数用于 函数参数列表中的表达式,如果对应 推导出模板参数,模板参数类型应匹配 模板参数的类型,除了一个 从数组绑定推导出的模板参数可以是任何整数 类型。 [ 例子:

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 {};

Live Example.

【讨论】:

  • @DyP [temp.clss.spec]/3 表示Int&lt;0&gt; 不需要与函数模板共舞,因为N 是主模板中的非类型参数,可以推导出来到1。问题是is_int&lt;T&gt; 在其主模板中没有非类型模板参数,而只有在其部分特化中。就是这个偏化中对N的推导需要函数模板参数推导,最终在14.8.2.5/17
  • @DyP 乍一看确实 似乎,因此令人惊讶的是,我试图在修改后的答案中澄清。顺便说一句,创建函数类型以推导部分特化类模板的参数的过程不应该比使用类模板特化作为参数编写函数模板更复杂,类似于重载决策游戏(其中参数被推导为other 重载的参数,而不是它们自己的参数)。
  • [从之前的评论转贴]core issue 1770 地址 14.8.2.5;提议的决议清楚地表明它适用于所有扣除,不仅适用于功能模板(或者它已经很清楚了,我只是太挑剔了;)
  • @DyP 至少你对完全理解的坚持让我更深入地挖掘,我今天学到了一些新东西,所以谢谢!
【解决方案2】:

Clang 不一致。 Since it accepts your code,我希望下面的代码必须输出f(Int&lt;long long&gt;) 而不是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&lt;0&gt; 与接受参数为Int&lt;N&gt; 的第二个重载不匹配。如果是这样,那么当它用作类模板的模板参数(在您的情况下)时,为什么它与Int&lt;N&gt; 匹配?

我的结论:

  • 如果 Clang 在 my 的情况下是正确的,那么在 your 的情况下它是不正确的。
  • 如果 Clang 在 your 的情况下是正确的,那么在 my 的情况下它是不正确的。

不管怎样,Clang 似乎有 bug。

另一方面,GCC 至少是一致的。这并不能证明它没有错误——这可能意味着它在两种情况下都有错误!除非有人提出标准并显示它也有错误,否则在这种情况下我会相信 GCC。

【讨论】:

  • 值得注意的是,GCC 有大量的测试用例,但更多的是在名称中带有“torture”的单独文件夹中加载,这些包含标准规定应该或不应该的真正复杂的代码位不工作,一个名为 Deja Gnu 的程序会验证错误发生在应该发生的地方,并且东西在应该的地方工作。从历史上看,GCC 一直是新 C++ 文档的沙箱,创建复杂代码进行测试是该过程的一部分。
  • 另外——我相信 Clang 也有测试(如果没有它就不会走到这一步:P)GCC 有经验。
  • 为了证明第二个重载对Int&lt;N&gt; 不可行,您必须删除第一个;否则你只会证明第一个是 better 匹配。如果确实删除了第一个重载,则早期的 clang++3.4 (coliru) 会抱怨:"注意:候选模板被忽略:替换失败:推导的非类型模板参数与其对应的模板参数的类型不同('unsigned long long' vs 'long long')"
  • [temp.deduct.type]/17: clang++ 正确拒绝了第二次重载。
  • @Nawaz 我对标准中部分专业化匹配的描述并不完全满意(恕我直言,这很模糊);但我认为 [temp.deduct.type]/17 打算也适用于匹配过程,在这种情况下,clang 应该拒绝 both、部分专业化和第二次重载。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-07-21
  • 2018-07-15
  • 2018-01-06
  • 2018-10-16
  • 2019-11-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多