I have some confusions regarding static constexpr
member variables in C++11.
template<typename T> struct cond_I { static constexpr T value = 0; }; // specialization template<typename T> struct cond_I< std::complex<T> > { static constexpr std::complex<T> value = {0,1}; };
cout << cond_I<double>::value << endl; // this works fine cout << cond_I< complex<double> >::value << endl; // linker error
However if I add the following line to first.hpp
everything works fine.
template<typename T1> constexpr std::complex<T1> cond_I< std::complex<T1> >::value;
What I understand (I may be wrong) is, that cond_I< std::complex<double> >::value
needs a definition, but in the previous case it only has the declaration. But then what about cond_I<double>::value
? Why it does not require a definition?
Again, in another header file, second.hpp
, I have:
// empty struct template<typename T> struct eps { }; // special cases template<> struct eps<double> { static constexpr double value = 1.0e-12; }; template<> struct eps<float> { static constexpr float value = 1.0e-6; };
In this case, following codes works perfectly without any definition of eps<>::value
.
cout << eps<double>::value << endl; // works fine cout << eps<float>::value << endl; // works fine
Can someone please explain me the different behaviors of static constexpr
member variables, in these scenarios?
These behaviors are also the same for gcc-5.2
and clang-3.6
.
A constexpr variable must have static storage at some point, it's baked in when the program is compiled.
A variable declared static within a module (but outside the body of a function) is accessible by all functions within that module. However, it is not accessible by functions from other modules. static members exist as members of the class rather than as an instance in each object of the class.
A static constexpr variable has to be set at compilation, because its lifetime is the the whole program. Without the static keyword, the compiler isn't bound to set the value at compilation, and could decide to set it later.
const can only be used with non-static member functions whereas constexpr can be used with member and non-member functions, even with constructors but with condition that argument and return type must be of literal types.
According to the standard 9.4.2/p3 Static data members [class.static.data] (Emphasis Mine):
If a non-volatile const static data member is of integral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression (5.20). A static data member of literal type can be declared in the class definition with the
constexpr
specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. [ Note: In both these cases, the member may appear in constant expressions. — end note ] The member shall still be defined in a namespace scope if it is odr-used (3.2) in the program and the namespace scope definition shall not contain an initializer.
As M.M earlier explained in the comments ostream::operator<<(ostream&, const complex<T>&)
passes by reference so value is considered odr-used in the program. Thus, as the wording above dictates you have to provide a definition.
Now as you’ve already found out fundamental types are passed by value, that it is why no definition required.
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