29

I have some confusions regarding static constexpr member variables in C++11.

In first.hpp

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}; }; 

In main() function

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:

In second.hpp

// 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.

In main() function

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.

4
  • 5
    I think the answer is that because ostream::operator<<(ostream&, const complex<T> &) passes by reference, this is considered odr-use of the argument, therefore you need a definition (for the reference to refer to). Commented Dec 2, 2015 at 21:55
  • 1
    Yes. Without taking any reference, first code works. float or double are passed by value, so the second code works. Thank you. Commented Dec 2, 2015 at 22:07
  • Related: stackoverflow.com/q/32812663/3093378 Commented Dec 3, 2015 at 4:45
  • @vsoftco Thank you for the note. That thread contains some useful explanations. I didn't find it earlier. Commented Dec 3, 2015 at 17:51

1 Answer 1

19

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.

Sign up to request clarification or add additional context in comments.

2 Comments

for anyone else confused at this: from C++17, due to changes that affect inline variables in general, the namespace definition becomes unnecessary (but still allowed / ignored for backwards compat)
Workaround: create a copy of it just before using template<typename T> T copy(T v){ return v; } It will create a temporary, and a reference to constant can be taken from a temporary. Will be optimized easily.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.