I'm wondering whether I can rely on constant initialization when there is a dependency between two constant non-local float variables with static storage duration in two different translation units - where one is dependent on (initialized to [the value of]) the other, and where for the latter, constant initialization is performed. I'm looking for answers providing and interpreting the relevant part of the standard, particularly the C++11 standard.
// Note: the non-use of constexpr is intended (C++03 compatibility)
// foo.h
struct Foo {
static const float kValue;
};
// foo.cpp
const float Foo::kValue = 1.5F;
// bar.h
struct Bar {
static const float kValue;
};
// bar.cpp
#include "foo.h"
const float Bar::kValue = Foo::kValue; // Constant initialization?
// main.cpp
#include "bar.h"
#include <iostream>
int main() { std::cout << Bar::kValue; }
Bar::kValue
, which have static storage duration, initialized by means of constant initialization? (Which in turn answers whether it is initialized by means of static or dynamic initialization)[basic.start.init]/1 states [emphasis mine]:
Constant initializationis performed:
if each full-expression (including implicit conversions) that appears in the initializer of a reference with static or thread storage duration is a constant expression (5.19) and the reference is bound to an lvalue designating an object with static storage duration or to a temporary (see 12.2);
if an object with static or thread storage duration is initialized by a constructor call, and if the initialization full-expression is a constant initializer for the object;
if an object with static or thread storage duration is not initialized by a constructor call and if either the object is value-initialized or every full-expression that appears in its initializer is a constant expression.
In interpret from the final bullet that Bar::kValue
is initialized by means of constant initialization if Foo::kValue
is a constant expression. I suspect I can find the answer to whether this is true or not in [expr.const], but here I'm stuck.
const float
does not meet the requirements for being a constant expression(This answer is based on @Oktalist's comment, as he refrained of making into an answer of his own)
In the following:
// foo.h struct Foo { static const float kValue; }; // foo.cpp const float Foo::kValue = 1.5F;
Foo::kValue
is indeed initialized by a constant expression by means of constant initialization, but Foo::kValue
is not a constant expression itself, because it is neither integral, enumeration, constexpr, nor temporary. [expr.const]/2 states [emphasis mine]:
A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression ([basic.def.odr]), but subexpressions of logical AND ([expr.log.and]), logical OR ([expr.log.or]), and conditional ([expr.cond]) operations that are not evaluated are not considered [ Note: An overloaded operator invokes a function. — end note ]:
...
(2.9): an lvalue-to-rvalue conversion ([conv.lval]) unless it is applied to
- a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression, or
- a glvalue of literal type that refers to a non-volatile object defined with constexpr, or that refers to a sub-object of such an object, or
- a glvalue of literal type that refers to a non-volatile temporary object whose lifetime has not ended, initialized with a constant expression;
As none of the sub-clauses for (2.9) applies here, Foo::kValue
is not a constant expression. It follows from [basic.start.init]/2 (as quoted in an earlier standard version in the question) that Bar::kValue
is not initialized by means of constant initialization, but as part of dynamic initialization.
Variables with static storage duration ([basic.stc.static]) or thread storage duration ([basic.stc.thread]) shall be zero-initialized ([dcl.init]) before any other initialization takes place [emphasis mine]:
Constant initialization is performed:
- ...
- if an object with static or thread storage duration is not initialized by a constructor call and if every full-expression that appears in its initializer is a constant expression.
Note that this particular example does not lead to the risk of the static initialization order fiasco, as Foo::kValue
is initialized as means of constant initialization and Bar::kValue
is initialized as part of dynamic initialization, and the former is guaranteed to complete before the start of dynamic initialization.
If the former would also be initialized as part of dynamic initialization, the initialization of the two would be indeterminately sequenced with respect to each other (and all other dynamic initialization).
Never rely on the fact, however, that this particular example has a well-defined initialization order, as subtle changes would invalidate this fact:
// foo.h
struct Foo {
static const float kDummyValue;
static const float kValue;
};
// foo.cpp
const float Foo::kDummyValue = 1.5F; // Constant initialization
const float Foo::kValue = kDummyValue; // (!) Dynamic initialization
// bar.h
struct Bar {
static const float kValue;
};
// bar.cpp
#include "foo.h"
const float Bar::kValue = Foo::kValue; // (!) Dynamic initialization
// main.cpp
#include "bar.h"
#include <iostream>
int main() { std::cout << Bar::kValue; }
As in this modifier example, the initialization of Foo::kValue
and Bar::kValue
is indeterminately sequenced with respect to each other, meaning Bar::kValue
could be initialized (with the "value" of Foo::kValue
) before Foo::kValue
is.