Ami*_*rsh 19 c++ templates partial-specialization template-specialization
// #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";
}
};
Run Code Online (Sandbox Code Playgroud)
下面的主要内容在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;
}
Run Code Online (Sandbox Code Playgroud)
# | 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 |
我将把我的答案献给案例#4,因为根据OP的编辑,编译器现在同意案例#6-8:
# | The code | g++ (6.1) | clang++ (3.8.0) |
4 | foo<double&, d, d> | #1 -- why? | #2 as expected |
看起来clang++ 3.8.0
行为正确并且gcc 6.1
拒绝了这种情况下完美的部分特化,因为以下错误已修复gcc 7.2
:
Bug 77435 - 依赖引用非类型模板参数与部分专业化不匹配
编译器代码中的这一关键更改存在差异:
// Was: else if (same_type_p (TREE_TYPE (arg), tparm))
else if (same_type_p (non_reference (TREE_TYPE (arg)), non_reference(tparm)))
Run Code Online (Sandbox Code Playgroud)
之前gcc 7.2
,当依赖类型与部分特化候选中的T&
类型参数匹配时,编译器会错误地拒绝它。T
这种行为可以用一个更清晰的例子来演示:
template <typename T, T... x>
struct foo {
static void scenario() { cout << "#1" << endl; }
};
// Partial specialization when sizeof...(x) == 1
template <typename T, T a>
struct foo<T, a> {
static void scenario() { cout << "#2" << endl; }
};
Run Code Online (Sandbox Code Playgroud)
在和T = const int
的行为相同的情况下:gcc 6.1
gcc 7.2
const int i1 = 1, i2 = 2;
foo<const int, i1, i2>::scenario(); // Both print #1
foo<const int, i1>::scenario(); // Both print #2
Run Code Online (Sandbox Code Playgroud)
但如果 的T = const int&
行为gcc 6.1
是拒绝正确的部分特化并选择基本实现:
foo<const int&, i1, i2>::scenario(); // Both print #1
foo<const int&, i1>::scenario(); // gcc 6.1 prints #1 but gcc 7.2 prints #2
Run Code Online (Sandbox Code Playgroud)
它会影响任何引用类型,以下是更多示例:
double d1 = 2.3, d2 = 4.6;
struct bar {};
bar b1, b2;
foo<double&, d1, d2>::scenario(); // Both print #1
foo<double&, d1>::scenario(); // gcc 6.1 prints #1 but gcc 7.2 prints #2
foo<bar&, b1, b2>::scenario(); // Both print #1
foo<bar&, b1>::scenario(); // gcc 6.1 prints #1 but gcc 7.2 prints #2
Run Code Online (Sandbox Code Playgroud)
您可以在此处运行此示例:https ://godbolt.org/z/Y1KjazrMP
gcc
似乎会犯这个错误,gcc 7.1
但从gcc 7.2
当前版本开始,由于上面的错误修复,它正确地选择了部分专业化。
总之,问题中案例 #4 的结果只是一个更普遍问题的症状,它的发生只是因为double&
是引用类型。为了证明这一说法,请尝试在OP的代码中添加以下行(以及bar
我b1
的示例中的定义):
cout << foo<bar&, b1, b1>::scenario() << endl;
Run Code Online (Sandbox Code Playgroud)
并观察在按预期打印时gcc 6.1
再次打印。"#1 the base template"
gcc 7.2
"#2 partial specialization"
关于OP编辑中的后续问题:
# | The code | g++ (11.2) | clang++ (12.0.1) |
4 | foo<double&, d, d> | #2 as expected | #1 -- why? |
我认为这g++ (11.2)
是正确的。
请注意,clang
它的答案并没有完全翻转,因为在您的链接中,您已经使用了c++20
标准,但是如果您将其改回c++14
原始问题中的那样,甚至clang++ 12.0.1
同意g++ 11.2
并选择部分专业化。
实际上,它也发生在clang
in 中c++17
,这似乎是从clang
该标准开始的一个问题,直到今天才得到解决。
如果您尝试将以下测试用例添加到您的代码中:
TEST (foo<const int, 2, 2>); // clang (c++17/20) prints #1 and gcc (any) prints #2
Run Code Online (Sandbox Code Playgroud)
clang
还选择基本模板而不是像gcc
此测试用例中的 while 那样的部分专业化:
TEST (foo<int, 2, 2>); // Both agree on #2
Run Code Online (Sandbox Code Playgroud)
他们都同意,我觉得很奇怪,因为添加const
到类型中的这不应该影响部分特化的适应性,而且看起来clang
这样做不仅仅为了参考,而且对于常量也是如此!并且仅当标准 >= C++17 时。
顺便说一句,这个问题也可以在我的示例中重现: https ://godbolt.org/z/W9q83j3Pq
观察一下,clang 8.0.0
仅仅通过改变语言标准,它就会与自己不一致,并且它会一直这样做,clang 13.0.0
甚至在不需要参数值相等的简单情况下也是如此。
这些奇怪的模板推论clang
引发了足够的“危险信号”,所以我必须得出结论,这g++ (11.2)
是正确的。
我的大胆猜测是 - C++17 引入了CTAD,这使得clang
类模板推导的行为有所不同,并且这个问题在某种程度上与其新实现有关,而旧的 C++14 实现保持不变。