我正在尝试理解变量模板。我尝试了下面的代码,它执行整数幂。为什么 clang++ 返回
0
而不是 8
?
#include <iostream>
template<int n, int e>
int r = n * r<n, e - 1>;
template<int n>
int r<n, 0> = 1;
int main() {
std::cout << r<2, 3>;
}
r<2, 3>
用2 * r<2, 2>
初始化。
r<2, 2>
的类型是int
,是非常量整型,r<2, 2>
没有标明constexpr
。因此 r<2, 2>
不能用于常量表达式。 r<2, 2>
的生命周期也不会在 r<2, 3>
的初始化期间开始,因此 [expr.const]/5.8 中的所有异常都不适用,并且 r<2, 3>
的初始化不是常量表达式。
这意味着
r<2, 3>
不是常量初始化的,可能有动态初始化。如果它有动态初始化,那么它有无序动态初始化,因为它是模板的特化。
同样的推理适用于
r<2, 2>
,它也可能有无序的动态初始化。
无序动态初始化的变量的初始化顺序是完全不确定的。在进行任何动态初始化之前,所有具有动态初始化的变量都被零初始化。因此,
r<2, 3>
有可能在 r<2, 2>
之前被初始化,在这种情况下,前者被 2 * 0
初始化。 (人们也可能会争论未定义的行为,因为 r<2, 2>
的初始化尚未完成。标准对此有点不清楚,正如 here 所争论的那样。)
另一方面,
r<2, 0>
的初始化是一个常量表达式,因此它从来没有动态初始化。因此,r<2, 1>
应该始终初始化为 2
,永远不要初始化为 0
。 (当您在 r<2, 1>
中使用 r<2, 3>
而不是 main
时,Clang 会给出预期值。)
如果你标记变量
constexpr
那么所有的特化都可以在常量表达式中使用并且常量初始化并且它应该按预期工作。可能 const
也足够了(因为它似乎在 Clang 上),因为 const
整数类型的变量也 可用于常量表达式 如果它有一个预先用常量表达式初始化。然而,关于变量模板特化的实例化点(参见 CWG 1845)以及“前面”的确切含义(参见 CWG 2186),存在一些悬而未决的问题。
参见 cppreference 以获取有关如何初始化非局部变量的参考,包括我上面描述的行为。
我认为运行时模板变量不能that递归。 评估需要按顺序进行。
(它可能偶然在 GCC 中工作。这可能是 UB 并且 clang 正在取消引用未初始化的值。)
它适用于
constexpr
,无论如何这就是你可能想要的。
#include <iostream>
template<int n, int e>
constexpr int r = n * r<n, e - 1>;
template<int n>
constexpr int r<n, 0> = 1;
int main() {
std::cout << r<2, 3>;
}