模板专用化的不同编译器行为

Different compilers behavior for template specialization

本文关键字:编译器 专用      更新时间:2023-10-16

我在尝试专用化模板时发现了不同的编译器行为。以下是说明该问题的最小代码:

#include <iostream>
template<typename A, typename B>
struct foo {
static const bool value = false;
};
template<typename B>
struct foo<int, B> {
static const bool value = !foo<B, B>::value;
};
int main() {
std::cout << foo<int, int>::value << std::endl;
return 0;
}

我有两个参数的通用模板和第一个int类型参数的专用模板。使用 g++ 编译器时,我得到

main.cpp: In instantiation of 'const bool foo<int, int>::value':
main.cpp:10:30:   recursively required from 'const bool foo<int, int>::value' 
main.cpp:10:30:   required from 'const bool foo<int, int>::value'
main.cpp:14:32:   required from here 
main.cpp:10:30: fatal error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum)
static const bool value = !foo<B, B>::value;

由于编译器使用value专用版本,因此获得了无限递归。我得到

Error   C2131   expression did not evaluate to a constant
Error   C2065   'value': undeclared identifier  

对于 MSVC。但是使用 clang 或 zapcc,代码编译时没有错误。原因是什么?在这种情况下,根据标准的正确行为是什么,还是行为未定义?

我不是语言律师,但这不应该编译。您正在尝试在编译时使用自身初始化值。我猜这是一个叮当虫。实际上,我不明白为什么GCC如果遇到一个循环,应该遵循这样的递归路径。

顺便说一下,MSVC告诉你(GodBolt):

<source>(14): note: see reference to class template instantiation 'foo<int,int>' being compiled

这是正确的收获。这一次,MSVC超越了其他编译器... :-P

编辑:@aschepler注意到即使没有模板,我们也会得到相同的行为(GodBolt):

struct bar { static const bool value = !bar::value; };

另一个编辑:似乎直到几年前,由于"语言律师的诡辩",这还是某种有效的代码。你看,标准曾经说过(第 6.6.2 节 [basic.start.static] 第 2 段):

具有静态存储持续时间的变量... 应为零初始化...在进行任何其他初始化之前。

Clang认为这意味着首先你对所有内容进行零初始化,然后考虑静态持续时间初始化 - 这意味着bar::value在自己的显式初始化之前隐式decltype(bar::value) { 0 }。这在 2026 年缺陷报告之后发生了变化。

指出这一点的功劳要归功于理查德·史密斯。