如何使用可变模板实现幂函数

huangapple go评论73阅读模式
英文:

How to implement a power function using variable templates

问题

I'm trying to understand variable templates. I tried the code below, which performs an integer power. Why does clang++ return 0 instead of 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>;
}

我正在尝试理解变量模板。我尝试了下面的代码,它执行整数幂运算。为什么clang++返回 0 而不是 8

英文:

I'm trying to understand variable templates. I tried the code below, which performs an integer power. Why does clang++ return 0 instead of 8?

#include &lt;iostream&gt;

template&lt;int n, int e&gt;
int r = n * r&lt;n, e - 1&gt;;

template&lt;int n&gt;
int r&lt;n, 0&gt; = 1;

int main() {
    std::cout &lt;&lt; r&lt;2, 3&gt;;
}

答案1

得分: 19

r&lt;2, 3&gt; 初始化为 2 * r&lt;2, 2&gt;

r&lt;2, 2&gt; 的类型为 int,这是一个非 const 的整数类型,并且 r&lt;2, 2&gt; 没有标记为 constexpr。因此,r&lt;2, 2&gt; 在常量表达式中不可用。r&lt;2, 2&gt; 的生命周期也不会在 r&lt;2, 3&gt; 的初始化过程中开始,因此 [expr.const]/5.8 中的任何异常都不适用,r&lt;2, 3&gt; 的初始化不是一个常量表达式。

这意味着 r&lt;2, 3&gt; 不是常量初始化的,可能具有动态初始化。如果它具有动态初始化,则具有 unordered dynamic initialization,因为它是模板的特化。

相同的推理适用于 r&lt;2, 2&gt;,它也可能具有无序的动态初始化。

具有无序动态初始化的变量初始化的顺序是完全不确定的。在进行任何动态初始化之前,所有具有动态初始化的变量都将被零初始化。因此,可能在 r&lt;2, 3&gt; 之前初始化 r&lt;2, 2&gt;,在这种情况下,前者将被初始化为 2 * 0

另一方面,r&lt;2, 0&gt; 的初始化是一个常量表达式,因此它永远不会有动态初始化。因此,r&lt;2, 1&gt; 应该始终被初始化为 2,而不是 0。 (在使用 r&lt;2, 1&gt; 而不是 r&lt;2, 3&gt; 时,Clang会给出预期的值。)

如果将变量标记为 constexpr,则所有的特化都可以在常量表达式中使用,并且可以正常工作。

参见 cppreference 以了解有关非局部变量初始化的参考,包括我上面描述的行为。


虽然应该使用 constexpr,但在这种特定情况下,const 也可能足够了(因为在 Clang 上似乎是这样),由于历史原因,如果具有先前使用常量表达式初始化的 const 整数类型的变量,它也是 usable in constant expressions。但关于变量模板特化的点的实例化(参见 CWG 1845)以及“preceding” 到底意味着什么的一些问题(参见 CWG 2186)仍然存在一些未解之谜。


也可以说你的程序具有未定义的行为,因为在 r&lt;2, 3&gt; 初始化中使用 r&lt;2, 2&gt; 可能尚未完成(动态)初始化。标准在这个问题上有点不够明确,正如在 这里 讨论的那样,但我不认为这是预期的解释。

英文:

r&lt;2, 3&gt; is initialized with 2 * r&lt;2, 2&gt;.

The type of r&lt;2, 2&gt; is int, which is a non-const integral type, and r&lt;2, 2&gt; is not marked constexpr. Therefore r&lt;2, 2&gt; is not usable in constant expressions. The lifetime of r&lt;2, 2&gt; also doesn't start during initialization of r&lt;2, 3&gt;, so none of the exceptions in [expr.const]/5.8 apply and the initialization of r&lt;2, 3&gt; is not a constant expression.

This means that r&lt;2, 3&gt; is not constant-initialized and may have dynamic initialization. If it has dynamic initialization, then it has unordered dynamic initialization, because it is a specialization of a template.

The same reasoning applies to r&lt;2, 2&gt; and it may also have unordered dynamic initialization.

The order in which variables with unordered dynamic initialization are initialized is completely indeterminate. Before any dynamic initialization is done all variables with dynamic initialization are zero-initialized. Therefore it is possible that r&lt;2, 3&gt; is initialized before r&lt;2, 2&gt;, in which case the former is initialized with 2 * 0.

On the other hand r&lt;2, 0&gt;'s initialization is a constant expression and therefore it never has dynamic initialization. As a consequence r&lt;2, 1&gt; should always be initialized to 2, never to 0. (And Clang gives the expected value when using r&lt;2, 1&gt; instead of r&lt;2, 3&gt; in your main.)

If you mark the variables constexpr then all specializations become usable in constant expressions and constant-initialized and it should work as expected.

See cppreference for a reference on how non-local variables are initialized including the behavior I describe above.


While you should use constexpr, const is probably also enough in this specific instance (as it seems to be on Clang), since for historical reasons a variable of const integral type is also usable in constant expressions if it has a preceding initialization with a constant expression. However there are some open questions on the point of instantiation of variable template specializations (see CWG 1845) and on what exactly "preceding" is supposed to mean (see CWG 2186).


One could also argue that your program has undefined behavior because (dynamic) initialization of r&lt;2, 2&gt; may not be done yet when used in r&lt;2, 3&gt; initialization. The standard is bit unclear on this, as argued here, but I do not think that this is intended interpretation.

答案2

得分: 3

I think runtime template variables can't be that recursive.
The evaluation needs to be sequential.

(It works in GCC by chance probably. This is probably UB, and clang is dereferencing an uninitialized value.)

It works with constexpr, which anyway is what you probably want.

#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>;
}

https://godbolt.org/z/xGhhqf8rd

英文:

I think runtime template variables can't be that recursive.
The evaluation needs to be sequential.

(It works in GCC by chance probably. This is probably UB and clang is dereferencing an uninitialized value.)

It works with constexpr, which anyway is what you probably want.

#include &lt;iostream&gt;

template&lt;int n, int e&gt;
constexpr int r = n * r&lt;n, e - 1&gt;;

template&lt;int n&gt;
constexpr int r&lt;n, 0&gt; = 1;

int main() {
    std::cout &lt;&lt; r&lt;2, 3&gt;;
}

https://godbolt.org/z/xGhhqf8rd

huangapple
  • 本文由 发表于 2023年2月24日 06:25:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/75550943.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定