这(部分)从 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
的构造函数调用和推导来确定 real 模板参数,即使 given 参数是 Bar
的专业化类型,MSVC 不同意这一点.
标准怎么说?
对于任何一个
static_asserts
,程序都应该是格式错误的。
首先请注意,这与 CTAD 无关。如果您显式指定
Bar<1>
作为模板参数类型,您仍然会获得相同的编译器行为。
类模板实参推导始终会完成,并将在此处推导
Bar<1>
。
但是,问题不在于模板参数对象如何从模板参数初始化以确定将被调用的
get_n
的相关专业化。
对于类类型究竟如何发生这种情况尚未明确,请参阅 CWG 问题 2459。
2023 年底通过论文P2308R1解决问题使得
get_n<b>()
的调用格式不正确。
基本上,新规则规定,应用于您的示例,我们首先想象一个定义为的变量
constexpr Bar<1> v = b;
然后我们进一步想象模板参数对象(也是前面推导的
Bar<1>
类型)是从表达式v
复制初始化的。如果模板参数对象的值不是template-argument-equivalent(即“相同”),则该程序是错误的。
在您的情况下会发生这种情况,因为复制构造函数(在
v
的初始化和模板参数对象的复制初始化中使用)将在新对象中生成与源对象中不同的值。
似乎还没有一个编译器实现了这一点。