I'm aware this question has been asked many times, but this seems to be a slightly different variation which I can't figure out.
Consider the following code:
#include <cstdio>
struct TestValue;
inline const TestValue* v_ptr = nullptr;
struct TestValue {
static const TestValue v1;
TestValue() {
v_ptr = this;
printf("TestValue Initialized at %p\n", this);
}
};
struct CallTest {
CallTest() {
printf("CallTest Initalized at %p\n", this);
printf("v_ptr = %p\n", v_ptr);
}
};
const inline TestValue TestValue::v1{};
const inline CallTest ct{};
int main() {}
I am using C++17 or later, which adds support for extern, static initialized inline variables. I am trying to understand the guarantees around initialization order when using the inline specifier "out of line". Notice that v1
is declared as a static variable of TestValue, then defined inline later, but before ct
. Surprisingly (to me at least), using Clang 14.0.3, the program prints:
CallTest Initalized at 0x404059
v_ptr = (nil)
TestValue Initialized at 0x404068
If I move v1
out of TestValue
such that it is declared and defined in the same line just before ct
, I get the expected output:
TestValue Initialized at 0x404059
CallTest Initalized at 0x404068
v_ptr = 0x404059
I am reasonably confident in my understanding of the standard that this second example is guaranteed to print TestValue first. But what about the first case?
I wasn't sure about the legality of forward declaring v1
, then defining it inline later, but that alone seems OK: https://eel.is/c++draft/dcl.inline#note-2
As for the ordering, my understanding is that v1
and ct
should be "partially ordered": since they are inline https://eel.is/c++draft/basic.start.dynamic#1
Then, since at least one of them is partially ordered (and the other is not unordered), they are initialized in the order of their definitions: https://eel.is/c++draft/basic.start.dynamic#3.1
Perhaps I'm misreading the definition of partially-ordered and unordered? Is v1
not partially-ordered since the inline specifier comes later in the definition - ie. the ordering only applies to inline at the declaration? In this case I still don't see how it would become unordered; the other possibility is ordered which works. Also specifying inline is needed to fix ODR violations, so it appears to be doing something. (I discovered this error from the above situation but where TestValue
and CallTest
and their respective definitions were split across multiple headers, the CallTest
header including TestValue
).
I also find that GCC respects the definition order of v1
and ct
while as above, Clang always initializes ct
first.
Edit: Another observation in Clang - If I make v1
and ct
constexpr (Removing the side effects from the constructors), the address of v1
is smaller than ct
- they are initialized in the expected order.
I also realized in the above example that const inline CallTest ct{}
has internal linkage, while v1
is external being a static member. I fixed this, and ct
is still "incorrectly" initialized first. Not sure if this is affects the expected initialization order.
I also ran this test:
extern const TestValue v1;
const inline TestValue v1{};
extern const inline CallTest ct{};
Which initializes v1
first. I don't understand why, if v1
is a static class variable, it would have a different initialization order than as an ordinary extern variable.
Looks like this should be fixed in Clang 16: https://github.com/llvm/llvm-project/commit/f9969a3d28e738e9427e371aac06d71269220123
Demo: https://clang.godbolt.org/z/vMrhTTW8v