18
votes

Static class members in C++ have caused a little confusion for me due to the standard's verbiage:

9.4.2 Static data members [class.static.data]

The declaration of a static data member in its class definition is not a definition...

However a constexpr is required to be initialized (AFAIK, couldn't find a quote from the standard) at its declaration (e.g., in the class definition).

Because of the restrictions on constexpr I had actually forgotten about the requisite for static members to be defined outside of the class, until I tried accessing a static constexpr array. This related question provides the correct way of defining the array member, but I'm interested as to the implications on this definition in a class template.

This is what I ended up with:

template<typename T>
class MyClass
{
private:
  static constexpr std::size_t _lut[256] = { /* ... */ };
  T _data;

public:
  static constexpr std::size_t GetValue(std::size_t n) noexcept
  {
    return _lut[n & 255];
  }

  // ...
};

template<typename T>
constexpr std::size_t MyClass<T>::_lut[256];

Is this the right syntax? Particularly the use of template in the definition feels awkward, but GCC seems to be linking everything appropriately.

As a follow-up question, should non-array static constexpr members be similarly defined (with template definition outside of class)?

2
One confusing thing here: The declaration in the class is not a definition but has an initializer. The declaration outside the class has no initializer but is a definition.aschepler
It is confusing (at least IMHO), but the constexpr specification requires the initializer to be at the point of declaration, not the point of definition. Presumably this is in relation to the way constant expressions are handled in terms of ODR (that is a constexpr member isn't required to have a definition so long as it is only used in compile-time constant expressions - see the answer ecatmur gave).monkey0506

2 Answers

14
votes

I think you want 9.4.2p3:

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.19). 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. [...] 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.

The definition of a template static data member is a template-declaration (14p1). The example given in 14.5.1.3p1 is:

template<class T> class X {
  static T s;
};
template<class T> T X<T>::s = 0;

However, as above a constexpr static or const static member whose in-class declaration specifies an initializer should not have an initializer in its namespace scope definition, so the syntax becomes:

template<class T> class X {
  const static T s = 0;
};
template<class T> T X<T>::s;

The difference with the non-array (i.e. integral or enumeration) static constexpr data member is that its use in lvalue-to-rvalue conversion is not odr-use; you would only need to define it if taking its address or forming a const reference to it.

15
votes

In case it helps anyone out, the following worked for me with GCC 4.7 using constexpr:

template<class T> class X {
  constexpr static int s = 0;
};
template<class T> constexpr int X<T>::s; // link error if this line is omitted

I'm not making any claims of whether this is "proper". I'll leave that to those more qualified.