【问题标题】:Template partial specialization for integral non-type parameters and non-integral non-types, difference between g++ and clang整型非类型参数和非整型非类型的模板偏特化,g++和clang的区别
【发布时间】:2016-09-19 01:02:46
【问题描述】:

下面是一个简单的模板偏特化:

// #1
template <typename T, T n1, T n2>
struct foo { 
    static const char* scenario() {
        return "#1 the base template";
    }
};

// #2
// partial specialization where T is unknown and n1 == n2
template <typename T, T a>
struct foo<T, a, a> { 
    static const char* scenario() {
        return "#2 partial specialization";
    }
};

下面的主要在g++ (6.1)clang++ (3.8.0) 上得到不同的结果:

extern const char HELLO[] = "hello";
double d = 2.3;

int main() {
    cout <<   foo<int, 1, 2>                    ::scenario() << endl;                   
    cout <<   foo<int, 2, 2>                    ::scenario() << endl;                   
    cout <<   foo<long, 3, 3>                   ::scenario() << endl;                  
    cout <<   foo<double&, d, d>                ::scenario() << endl;               
    cout <<   foo<double*, &d, &d>              ::scenario() << endl;             
    cout <<   foo<double*, nullptr, nullptr>    ::scenario() << endl;   
    cout <<   foo<int*, nullptr, nullptr>       ::scenario() << endl;      
    cout <<   foo<nullptr_t, nullptr, nullptr>  ::scenario() << endl; 
    cout <<   foo<const char*, HELLO, HELLO>    ::scenario() << endl;
}

g++clang++ 的结果

<b>#</b> | <b>The code</b> | <b>g++ (6.1)</b> | <b>clang++ (3.8.0)</b> |
1 | foo&lt;int, 1, 2&gt; | #1 as expected | #1 as expected |
2 | foo&lt;int, 2, 2&gt; | #2 as expected | #2 as expected |
3 | foo&lt;long, 3, 3&gt; | #2 as expected | #2 as expected |
4 | foo&lt;double&amp;, d, d&gt; | #1 -- why? | #2 as expected |
5 | foo&lt;double*, &amp;d, &amp;d&gt; | #2 as expected | #2 as expected |
6 | foo&lt;double*, nullptr, nullptr&gt; | #2 as expected | #1 -- why? |
7 | foo&lt;int*, nullptr, nullptr&gt; | #2 as expected | #1 -- why? |
8 | foo&lt;nullptr_t, nullptr, nullptr&gt; | #2 as expected | #1 -- why? |
9 | foo&lt;const char*, HELLO, HELLO&gt; | #2 as expected | #2 as expected |

哪个是正确的?

代码:https://godbolt.org/z/4GfYqxKn3


编辑,2021 年 12 月:

自原始帖子以来的几年里,结果发生了变化,and were even identical for gcc and clang at a certain point in time,但再次检查,g++ (11.2)clang++ (12.0.1) changed their results on references (case 4), but still differ on it。目前gcc 似乎一切正常,而clang 在参考案例中是错误的。

<b>#</b> | <b>The code</b> | <b>g++ (11.2)</b> | <b>clang++ (12.0.1)</b> |
1 | foo&lt;int, 1, 2&gt; | #1 as expected | #1 as expected |
2 | foo&lt;int, 2, 2&gt; | #2 as expected | #2 as expected |
3 | foo&lt;long, 3, 3&gt; | #2 as expected | #2 as expected |
4 | foo&lt;double&amp;, d, d&gt; | #2 as expected | #1 -- why? |
5 | foo&lt;double*, &amp;d, &amp;d&gt; | #2 as expected | #2 as expected |
6 | foo&lt;double*, nullptr, nullptr&gt; | #2 as expected | #2 as expected |
7 | foo&lt;int*, nullptr, nullptr&gt; | #2 as expected | #2 as expected |
8 | foo&lt;nullptr_t, nullptr, nullptr&gt; | #2 as expected | #2 as expected |
9 | foo&lt;const char*, HELLO, HELLO&gt; | #2 as expected | #2 as expected |

【问题讨论】:

  • @EissaN,请注意这是结构的特化,而不是函数。虽然我同意这确实是在怪癖区......
  • 事实上,MSVC 产生了所有预期的结果。
  • EDG 在 C++14 严格模式下也会按预期选择部分特化。
  • 我想补充一点,gcc 7.2 和 clang 4.0.0 分别是最早的版本,以提供所有预期的结果:godbolt.org/z/g6imAK
  • 快进到 2020 年,g++ 7.5.0clang 8.0.0 给出相同(正确)的结果

标签: c++ templates template-specialization partial-specialization


【解决方案1】:

我认为在clang中nullptr更像是内置变量,如内置类型intnullptr 实际上没有类型。 nullptr_t 的声明(没有定义)是struct nullptr_t nullptr_t;。第一个问题的答案是正确的……答案是两者都是正确的,因为有标准,但这些是由不同公司制造的不同编译器,因此GNU G++Clang 之间可能存在差异。 Clang 应该与 G++ 完全兼容,但事实并非如此。

【讨论】:

  • 实际上在 MS VS 2015 中使用 Clang 它不会在编译器的输出中产生错误。但在运行时,您可以看到它使用基本模板。正如我在回答中提到的nullptr 没有类型。
  • nullptr 需要有一个类型,nullptr_t 按标准来说是这样......有点奇怪。
【解决方案2】:

#4 格式不正确,我很惊讶它可以编译。一方面,双精度不能用作非类型模板参数。另一方面,类模板使用与函数模板相同的规则进行部分排序。该标准提供了一个为执行排序而生成的“虚构”函数的示例:

template<int I, int J, class T> class X { };
template<int I, int J> class X<I, J, int> { }; // #1
template<int I>        class X<I, I, int> { }; // #2
template<int I, int J> void f(X<I, J, int>); // A
template<int I>        void f(X<I, I, int>); // B

对于您的示例,它看起来像这样:

template <typename T, T n1, T n2>
struct foo { 
};

template <typename T, T n1, T n2>
void bar(foo<T, n1, n2>)
{
    std::cout << "a";
}

template <typename T, T a>
void bar(foo<T, a, a>)
{
    std::cout << "b";
}

模板参数推导用于确定哪个功能比另一个更专业。 double&amp; 应该被推导出为 double,因此两个专业化应该是相等的,对于 bar(foo&lt;double&amp;, d, d&gt;{}); 来说也是模棱两可的。看看 GCC 和 Clang 抱怨:

GCC 错误

main.cpp:14:6: 注意:模板参数推导/替换 失败:main.cpp:26:29:注意:不匹配的类型 'double&' 和 '双'

 bar(foo<double&, d, d>{});
                         ^

Clang 错误

注意:候选模板被忽略:替换失败 [with T = double &]:推导的非类型模板参数不具有相同的 类型作为其对应的模板参数('double' vs 'double &')

同样,如果您删除引用,他们都正确地抱怨使用 double 作为非类型模板参数。

我不打算测试其余的,但您可能会发现类似的结果。

【讨论】:

  • “一方面,双精度不能用作非类型模板参数。”但是对double 的左值引用可以。
  • ...而您投诉的扣费失败是由a defect in the standard引起的。
  • 第二件事是有道理的:“相同的引用”通常只公开为“指针相同”。 OP 似乎希望这个“相同”与模板模式匹配一​​起工作,这在哲学上似乎过分了。
猜你喜欢
  • 2017-07-18
  • 1970-01-01
  • 1970-01-01
  • 2019-10-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-15
相关资源
最近更新 更多