I am a bit confused by the static
in-class initialization of a const
member. For example, in the code below:
#include <iostream>
struct Foo
{
const static int n = 42;
};
// const int Foo::n; // No ODR
void f(const int& param)
{
std::cout << param << std::endl;
}
int g(const int& param)
{
return param;
}
template<int N>
void h()
{
std::cout << N << std::endl;
}
int main()
{
// f(Foo::n); // linker error, both g++/clang++
std::cout << g(Foo::n) << std::endl; // OK in g++ only with -O(1,2 or 3) flag, why?!
h<Foo::n>(); // this should be fine
}
Live example
I do not define Foo::n
(the line is commented). So, I expect the call f(Foo::n)
to fail at link time, and indeed it does. However, the following line std::cout << g(Foo::n) << std::endl;
compiles and links fine only by gcc (clang still emits a linker error) whenever I use an optimization flag such as -O1/2/3
.
h<Foo::n>
call, in which case the code should link?I suppose that the compiler performs the following actions during the optimization:
The value const static int n
is inlined everywhere. No memory is allocated for the variable n
, references to it becomes invalid. The function f()
need a reference to n
so the program is not compiled.
The function g
is short and simple. It is effectively inlined and optimized. After the optimization, the function g
does not need a reference to n
, it just returns constant value 42.
The solution is to define the variable outside the class:
struct Foo
{
const static int n;
};
const int Foo::n = 42;
ODR violations do not require a diagnostic, from the draft C++ standard standard section 3.2
[basic.def.odr] (emphasis mine going forward):
Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required.
So inconsistent behavior at different optimization levels is perfectly conformant behavior.
Informally a variable is odr-used if:
its address is taken, or a reference is bound to it, and a function is odr-used if a function call to it is made or its address is taken. If an object or a function is odr-used, its definition must exist somewhere in the program; a violation of that is a link-time error.
So both f
and g
will be odr-uses and require a definition.
The relevant C++14 quote on odr-use would be from section [basic.def.odr]:
A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.19) that does not invoke any nontrivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (4.1) is applied to e, or e is a discarded-value expression [...]
The wording in C++11 is similar, the changes from C++11 to C++14 are reflected in defect report 712.
Before C++11 it is a bit more complicated but in principle the same for this case.
Formally, ODR violations are undefined behaviour, so the compiler may exhibit any behaviour it likes. That's why the behaviour changes with optimization level and compiler- the compiler has no obligation to maintain a particular behaviour.
There is no definition at all. GCC 4.9.2 doesn't compile and link that with any flags.
Note, that:
const static int n = 42;
is a declaration and initializer, but not a definition.
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