在尝试编译以下代码时,gcc/clang和msvc之间存在分歧:
struct foo {
};
// primary template
template<auto>
struct nttp {
static constexpr int specializaion = 0;
};
// specialization
template<foo f>
struct nttp<f> {
static constexpr int specializaion = 1;
};
int main() {
// Does not compile with msvc 19.30
nttp<5> x{};
}
这里有一个完整的例子。
Msvc 报错了,说从 int
到 foo
没有可行的转换方式,这显然是正确的,但就我对偏特化规则的理解来看,这应该与此处没有任何关系。
引用cppreference的话:
当实例化类或变量(自C++14起)模板时,如果有部分特化可用,则编译器必须决定是使用主模板还是其中的一个部分特化。
- 如果只有一个特化与模板参数匹配,则使用该特化。
- 如果有多个特化匹配,则使用偏序规则来确定哪个特化更为特化。如果最专业的特化是唯一的(如果不唯一,则无法编译程序),则使用它。
- 如果没有特化匹配,则使用主模板。
我认为在这种情况下,int
没有匹配到任何特化,因此应该选择主模板。
有趣的是,可以通过将特化中的 auto
限定为一个概念(concept)来解决这个问题:
template<class T>
concept Foo = std::is_same_v<T, foo>;
template<auto>
struct nttp {
static constexpr int specializaion = 0;
};
template<Foo auto f>
struct nttp<f> {
static constexpr int specializaion = 2;
};
// compiles with all compilers
static_assert(nttp<5>{}.specializaion == 0);
函数模板也能够正常工作:
constexpr int test(auto) {
return 0;
}
constexpr int test(foo) {
return 1;
}
// compiles with all compilers
static_assert(test(5) == 0);
长话短说:根据经验和直觉,我认为这是MSVC中的一个错误,但通常情况下可能会涉及UB(未定义行为)。所以问题是:clang/gcc正确、还是MSVC正确,或者甚至全部正确?