繁体   English   中英

"整型非类型参数和非整型非类型的模板偏特化,g++和clang的区别"

[英]Template partial specialization for integral non-type parameters and non-integral non-types, difference between g++ and clang

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

// #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++的结果

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

哪一个是对的?

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


编辑,2021 年 12 月:

自原始帖子以来的几年中,结果发生了变化,甚至在某个时间点gccclang是相同的,但是再次检查, g++ (11.2)clang++ (12.0.1)改变了他们在引用上的结果(案例4),但仍然存在差异 目前gcc似乎一切正常,而参考案例中的clang是错误的。

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

让我们从根据 C++ 标准确定什么是正确的开始,并将其与实际的编译器进行比较。 我很确定,它应该像您所期望的那样工作,即第一种情况是#1<\/code> ,其余情况是#2<\/code> (尽管有一些警告,见后)。

正如evzh<\/code>在他的评论中指出的那样(2020 年 7 月 1 日 3:30),gcc 7.2 和 clang 4.0.0 是最早可以正常工作的版本。 它们可以正常工作,一直到带有-std=c++11<\/code>的最新版本。

这是警告。 当您将-std<\/code>从c++14<\/code>增加到c++17<\/code>时,Clang 的工作方式不同。 EDIT, Dec-2021<\/strong>的问题中,您使用的是-std=c++20<\/code> ,它在c++17<\/code>之后。 这就是为什么你经历了why?<\/code> 案件。

我的假设是编译器在 2016 年存在错误,它们与上述已建立的正确行为不同,并且带有-std=c++17<\/code>或更高版本的 clang 12.0.1 在foo<double&, d, d><\/code>的情况下仍然存在错误.

Clang 是一个非常迂腐的编译器,所以我可能错了。 有一点机会,某些东西改变了标准,这使得 clang 正确而其他编译器错误。 这值得在一个单独的问题中提出。

这是一个简化的测试来演示您的所有案例: demo<\/a> 。 我添加了四个编译器来展示foo<double&, d, d><\/code>行为方式。

这是另一个专门针对foo<double&, d, d><\/code>的演示<\/a>。

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

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&应该被推导为double ,因此两个特化应该相等,也就是bar(foo<double&, d, d>{});歧义bar(foo<double&, d, d>{}); . 瞧,GCC 和 Clang 抱怨:

海湾合作委员会错误

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

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

叮当错误

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM