14

Consider the following example:

#include <cstdio>

template <int N>
int fib = fib<N - 1> + fib<N - 2>;

template <> int fib<2> = 1;
template <> int fib<1> = 1;

int main()
{
    std::printf("%d %d %d", fib<4>, fib<5>, fib<6>);
}
  • GCC 7.x, 8.x, 9.x, and 10.x all print out the expected result of 3 5 8.

  • Clang 5.x, 6.x, 7.x, 8.x, 9.x, and 10.x all print out 1 3 4 as a result.

live example on godbolt.org


Clang's behavior is surprising.

Is there any subtle interaction between variable template instantiation, global variables, and recursion in the C++ standard that I am missing?

Or is this a long-standing Clang bug?

By the way, marking fib as constexpr solves the issue (on godbolt.org).

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 3
    My gut says this is the static initialization order fiasco... whether it's permitted in this case by something in the standard or it's a Clang bug, I'm not sure. But `constexpr` fixing it would make sense since it conceptually imposes an ordering. – cdhowie Jun 06 '20 at 16:52
  • @cdhowie wouldn't it result in infinite recursion when they are initialized in wrong order? – 463035818_is_not_an_ai Jun 06 '20 at 17:10
  • 2
    @idclev463035818 No. The question is, when instantiating `fib<4>` causes `fib<3>` to be instantiated, does the standard _guarantee_ that `fib<3>` is initialized before `fib<4>`? If not, `fib<4>` uses it before initialization and reads an indeterminate value. – cdhowie Jun 06 '20 at 17:25
  • Just for info, `clang-cl` gives this (thrice, once for each instance): **warning : declaration requires a global constructor [-Wglobal-constructors]** – Adrian Mole Jun 06 '20 at 17:29
  • @cdhowie can you have static initialization order fiasco with single compilation unit? – Tony Tannous Jun 06 '20 at 18:00
  • [CWG DR 1744](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1744), _Unordered initialization for variable template specializations_, adds _"Note: an explicitly specialized static data member or variable template specialization has ordered initialization."_ to [\[basic.start.static\]](https://timsong-cpp.github.io/cppwp/n4659/basic.start.dynamic#1) whereas Clang lists the status of CWG issue 1744 available in Clang as _Unknown_. However, even if initialization of `fib` is ordered due to the explicit specializations, the order will depend on when they are instantiated, right? – dfrib Jun 06 '20 at 18:17
  • @dfri Right but I think this is about the non-specialized variables. – cdhowie Jun 06 '20 at 18:20
  • @TonyTannous Maybe! :) – cdhowie Jun 06 '20 at 18:21
  • Note that variable templates is not relevant here; you will see the same behaviour even back in C++03 for non-const static data members of class templates; [*DEMO*](http://coliru.stacked-crooked.com/a/7f83d3dd6de62e4d). The classical `Fib` implementation simply marks the `value` member as `const` such that constant initialization (and not static initialization) applies. – dfrib Jun 06 '20 at 19:12
  • I'd say dup of https://stackoverflow.com/questions/54981973/g-and-clang-different-behaviour-with-recursive-initialization-of-a-static-me, but the answer there is terrible. 0 citations from the Standard, only from Wikipedia^W cppreference. – Language Lawyer Jun 07 '20 at 06:17
  • @LanguageLawyer Nice find: still worth marking as a duplicate imho (to get them linked), particularly as this dupe now has a good answer of its own. – dfrib Jun 07 '20 at 08:56
  • Does this answer your question? [g++ and clang++ different behaviour with recursive initialization of a static member](https://stackoverflow.com/questions/54981973/g-and-clang-different-behaviour-with-recursive-initialization-of-a-static-me) – cigien Jun 08 '20 at 02:40

1 Answers1

5

From [basic.start.dynamic]/1:

Dynamic initialization of a non-local variable with static storage duration is unordered if the variable is an implicitly or explicitly instantiated specialization, is partially-ordered if the variable is an inline variable that is not an implicitly or explicitly instantiated specialization, and otherwise is ordered. [ Note: An explicitly specialized non-inline static data member or variable template specialization has ordered initialization. — end note ]

fib<4>, fib<5> and fib<6> are non-local variables with static storage duration that are implicitly instantiated specializations, so their dynamic initialization is unordered.

The behavior is not undefined; there must be some some unspecified ordering of initialization that produces the output seen (per [basic.start.dynamic]/3.3 the initializations are indeterminately sequenced). In fact, clang initializes in the following order (noting that a variable before dynamic initialization has the value 0 from static initialization):

fib<1> = 1 (actually static-initialized under [basic.start.static]/3)
fib<2> = 1 (similarly)
fib<4> = fib<2> + fib<3> = 1 + 0 = 1
fib<3> = fib<1> + fib<2> = 1 + 1 = 2
fib<5> = fib<3> + fib<4> = 2 + 1 = 3
fib<6> = fib<4> + fib<5> = 1 + 3 = 4

This is equally as valid as gcc (and MSVC) initializating in the order fib<3>, fib<4>, fib<5>, fib<6>.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • As the dynamic initialization of non-explicit specializations is unordered, how is it not UB (/static initialization fiasco) when the initialization of one (non-explicit) specialization make use of another yet-to-be initialized (non-explicit) specialization; particularly a "could happen" scenario due to the indeterminately sequenced initializations of the non-explicit specializations? – dfrib Jun 06 '20 at 18:45
  • 1
    @dfri See [this](https://eel.is/c++draft/basic.start.static#3). In particular the example in the note. The compiler can use either the dynamically initialized, or the statically initialized value. Actually the answer quotes this, without linking to it :) – cigien Jun 06 '20 at 18:51
  • 1
    @dfri the SIOF isn't actually UB (well, as long as threads aren't involved); it can lead to UB through accessing class members before invariants are established. Here we have simple `int` objects that don't have internal invariants; they are either `0` (from static initialization) or the value from their dynamic initialization once that occurs. – ecatmur Jun 06 '20 at 18:53
  • @cigien I see, thanks, I was actually rather missing the preceding section [\[basic.start.static\]/2](https://eel.is/c++draft/basic.start.static#2) regarding static initialization (zero initialization, here, as constant initialization does not apply), and that the resulting zero-initialized value can be, indeterminately (/unspecified) used as a fallback as per \[basic.start.static\]/3. Variable templates isn't really essential in this question, as this is the same behaviour as for [a non-const static data member of a class template](http://coliru.stacked-crooked.com/a/8cb25c2ed655db26). – dfrib Jun 06 '20 at 19:06
  • does that imply that recursive template variables are no good in general? – 463035818_is_not_an_ai Jun 06 '20 at 19:44
  • 1
    @idclev463035818 it means that to have consistent initialization they need to be constant-initialized, which effectively means they need to be constexpr. – ecatmur Jun 06 '20 at 19:54
  • 1
    @idclev463035818 Note also, as per [my comment above](https://stackoverflow.com/questions/62234931/recursive-computation-using-variable-templates-gcc-vs-clang/62236303#comment110070851_62234931), that this does not have any particular relation to variable templates; the same goes for non-constant static data members of class templates; each non-explicit specialization that uses recursive initialization shall do so as constant initialization, to avoid issues like these. – dfrib Jun 06 '20 at 19:56