Experimentally, I know how to declare/initialize/define static
data member of various kinds (mainly by reading the compiler diagnosis) yet I realize that I don't really understand the rules behind these mechanics and the One-Definition-Rule but I have issues with understanding behind the logic of static
data member initialization and respect of ODR.
My confusion is increased by differences in behavior according to const
-ness, constexpr
-ness.
I found several posts on SO speaking about static
data members and ODR but I'm unsure that they address exactly my questions below. At least, none that I read was able to give me the big picture, all in one place.
https://en.cppreference.com/w/cpp/language/static provides the rules regarding static
data members but I don't fully understand it with respect to One-Defintion-Rule.
Let say I have the following files.
ClassODR.h
struct CData {
#ifdef IN_CLASS_DEFINITION
static int m_Value = 3;
#else
static int m_Value;
#endif
#ifdef IN_CLASS_CONST_DEFINITION
static int const m_kValue = 3;
#else
static int const m_kValue;
#endif
#ifdef IN_CLASS_CONSTEXPR_DEFINITION
static double constexpr m_kfValue = 3.14;
#else
static double constexpr m_kfValue;
#endif
};
#ifdef OUT_OF_CLASS_IN_HEADER_DEFINITION
int CData::m_Value = 3;
#endif
#ifdef OUT_OF_CLASS_CONST_IN_HEADER_DEFINITION
int const CData::m_kValue = 3;
#endif
#ifdef OUT_OF_CLASS_CONSTEXPR_IN_HEADER_DEFINITION
constexpr double m_kfValue = 3.14;
#endif
ClassODR.cpp
#ifndef OUT_OF_CLASS_IN_HEADER_DEFINITION
int CData::m_Value = 3;
#endif
#ifndef IN_CLASS_CONST_DEFINITION
#ifndef OUT_OF_CLASS_CONST_IN_HEADER_DEFINITION
int const CData::m_kValue = 3;
#endif
#endif
#ifndef IN_CLASS_CONSTEXPR_DEFINITION
#ifndef OUT_OF_CLASS_CONSTEXPR_IN_HEADER_DEFINITION
int const CData::m_kfValue = 3.14;
#endif
#endif
OUT_OF_whatever_IN_HEADER_DEFINITION
could be only defined if IN_whatever_DEFINITION
is not.
TestODRUse#N.cpp
void TestODRUse#N() {
std::cout << CData::m_Value << '\n';
std::cout << CData::m_kValue << '\n';
std::cout << CData::m_kfValue << '\n';
}
main.cpp
int main() {
TestODRUse1();
TestODRUse2(); // testing for ODR violations
}
Other trivial header files and #include
directives required as glue between the different files are omitted on purpose for clarity sake.
The full example is available here.
These are some observations (for the record and the sake of exhaustiveness) and some questions.
1- If any OUT_OF_whatever_IN_HEADER_DEFINITION
is set, there is an ODR violation: corresponding variable is defined in TestODR1 and TestODR2 translation unit. No question there. Besides, the situation can be alleviate by declaring the variable inline
from C++17 on.
2- I was wondering why the static
non-const
member could not be initialized in-line, I found that answer that explain that it is in order to avoid ODR violation: https://stackoverflow.com/a/61519186
3- But on the other hand, situation changes as soon as the data is declared const(expr)
.
static const(expr)
member can (must) be initialized in-line: thus each translation unit see a definition but there is no ODR violation: why?
5- Eventually the following paragraph, from the cppreference page linked above, regarding constexpr static
data member, puzzles me:
If a const non-inline(since C++17) static data member or a constexpr static data member(since C++11)(until C++17) is odr-used, a definition at namespace scope is still required, but it cannot have an initializer. namespace scope for constexpr The following example is given:
struct X
{
static const int n = 1;
static constexpr int m = 4;
};
const int *p = &X::n, *q = &X::m; // X::n and X::m are odr-used
const int X::n; // … so a definition is necessary
constexpr int X::m; // … (except for X::m in C++17)
Yet I think that I never had to redefined, out-of-class an in-class initialized static constexpr
data member.
To sum it up, I'd like clarification on the ODR for static
data members according to their const(expr)-ness. Thanks to specify what may be the differences regarding the different C++ versions.