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>;
}
r<2, 3> is initialized with 2 * r<2, 2>.
The type of r<2, 2> is int, which is a non-const integral type, and r<2, 2> is not marked constexpr. Therefore r<2, 2> is not usable in constant expressions. The lifetime of r<2, 2> also doesn't start during initialization of r<2, 3>, so none of the exceptions in [expr.const]/5.8 apply and the initialization of r<2, 3> is not a constant expression.
This means that r<2, 3> 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<2, 2> 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<2, 3> is initialized before r<2, 2>, in which case the former is initialized with 2 * 0.
On the other hand r<2, 0>'s initialization is a constant expression and therefore it never has dynamic initialization. As a consequence r<2, 1> should always be initialized to 2, never to 0. (And Clang gives the expected value when using r<2, 1> instead of r<2, 3> 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<2, 2> may not be done yet when used in r<2, 3> initialization. The standard is bit unclear on this, as argued here, but I do not think that this is intended interpretation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With