Confusion about declaration and definition of static const data memebers

Viewed 1447

Scott Meyers writes in Effective Modern C++ (Item 30, at page 210) that there's

no need to define integral static const data members in classes; declarations alone suffice,

then the sample code is

class Widget {
  public:
    static const std::size_t MinVals = 28; // MinVals' declaration;
    ...
};
...                                        // no defn. for MinVals
std::vector<int> widgetData;
widgetData.reserve(Widget::MinVals);       // use of MinVals

I was convinced that static const std::size_t MinVals = 28; is declaration and also a definition, as it is giving a value to MinVals, but the comment seems to claim that's only a declaration; the second comment actually claims there's no definition. The text after the code, indeed reads

MinVals lacks a definition.

Which confirms that static const std::size_t MinVals = 28; is not a definition, so I'm a bit confused.

cppreference doesn't help me much (my bold-italic):

If a static data member of integral or enumeration type is declared const (and not volatile), it can be initialized with an initializer in which every expression is a constant expression, right inside the class definition:

struct X
{
   const static int n = 1;
   const static int m{2}; // since C++11
   const static int k;
};
const int X::k = 3;

but first two lines in the class look definitions to me.

The same goes for a following example on cppreference:

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)

where I'd have said static const int n = 1; is a definition, but it is not, based on the second to last comment.

3 Answers

no need to define integral static const data members in classes; declarations alone suffice,

Declarations alone suffice only if that object is not ODR-used, that is, if a data member is not used in a context that would require its address to exist (like binding to a reference or applying operator &). The presence of an initializer does not equal a definition.

In the example from the book, it's clear that MinVals is not ODR-used, i.e., the compiler can use its value directly, without having to create an object in memory, and so the statement:

widgetData.reserve(Widget::MinVals);

becomes:

widgetData.reserve(28);

If, however, in any other place, MinVals were ODR-used, that would make the program ill-formed.

All other examples from cppreference clearly indicate when a value is ODR-used and a definition is required, and when not:

struct X
{
    const static int n = 1;
    const static int m{2}; // since C++11
    const static int k;
};
const int X::k = 3;

n and m are declarations with initializers. An attempt to obtain the address of either n or m should fail.

struct X {
    static const int n = 1;
    static constexpr int m = 4;
};
const int *p = &X::n, *q = &X::m;
const int X::n;
constexpr int X::m;

Expressions &X::n and &X::m count as ODR-use of n and m, respectively (that is, an address is requested). For a constexpr static data members, a definition was required prior to C++17. From C++17, static constexpr data members are implicitly inline, which means, no out-of-class definition is needed, as they are definitions themselves.

Looking at this Draft Standard, it appears that your example falls into a grey area. While there is no explicit mention of lines such as:

    static const std::size_t MinVals = 28;

There is an example given which is very similar:

6.1 Declarations and definitions
...
2 A declaration is a definition unless
...
2.3 — it declares a non-inline static data member in a class definition
...
Example: All but one of the following are definitions:

int a; // defines a
extern const int c = 1; // defines c
...

The second example is close to your code, but with a significant difference in having the extern qualifier. Also, note that the above states that a declaration is (by default) also a definition unless one of the listed conditions applies; I would say (though I'm no Language-Lawyer) that none of those conditions are met exactly in your case, so your declaration is also a definition.

NOTE: The linked document is only a draft standard; make sure to read the 'disclaimer' given at the foot of its first page!

From The Standard Chapter "12.2.3.2 Static data members":

The member shall still be defined in a namespace scope if it is odr-used in the program and the namespace scope definition shall not contain an initializer.

By using it, it shall be defined.

Related