当给出明确的参数时,这(部分)从 CTAD 实例化其他专业化中减少。
https://godbolt.org/z/5To7nKEP3
template<int N>
struct Bar {
int n = N;
constexpr Bar() {}
constexpr Bar(const Bar& b): n{b.n - 1} {}
};
template<Bar b> constexpr int get_n() { return b.n; }
constexpr auto b = Bar<1>{};
static_assert(b.n == 1);
#ifdef _MSC_VER
static_assert(get_n<b>() == 1);
#else
static_assert(get_n<b>() == 0);
#endif
Clang 和 GCC 似乎总是调用并从Bar
的构造函数中推断出真正的模板参数,即使给定的参数是的专门化类型Bar
,但 MSVC 不同意。
标准是怎么说的?
对于任一情况来说,该程序都应该是格式错误的
static_asserts
。首先,请注意,这与 CTAD 无关。如果您明确指定
Bar<1>
为模板参数类型,您仍会得到相同的编译器行为。类模板参数推导总是完成并且将
Bar<1>
在这里推导。然而,现在的问题是如何从模板参数初始化模板参数对象以确定
get_n
将调用的相关特化。对于类类型,这种情况究竟应该如何发生尚未明确说明,请参阅CWG 问题 2459。我猜这就是为什么你在示例中看到编译器发散的原因。
2023 年底论文P2308R1对该问题的解决方案使得该调用格式
get_n<b>()
不正确。基本上,新规则规定,应用于你的例子,我们首先想象一个定义为
然后我们进一步想象模板参数对象(也是
Bar<1>
前面推导的类型)是从表达式复制初始化的v
。如果模板参数对象不等同于模板参数v
(即它们具有“相同”的值(和类型)),则程序格式不正确。在您的情况下会发生这种情况,因为复制构造函数(用于
v
模板参数对象的初始化和复制初始化)将在新对象中产生与源对象不同的值。似乎还没有任何编译器实现这一点。