What is the right way to initialize static data members in C++? I'm also interested in how it has changed from C++98, to C++11 to C++14.
Here is an example:
// bufferedOutput.h
class BufferedOutput
{
// Static member declaration.
static long bytecount;
};
// bufferedOutput.cpp
long BufferedOutput::bytecount = 50;
Are there other ways to initialize static data members?
The rules have always been as follows:
A const static data member (SDM) of integral or enumeration type can be initialised in class with a constant expression.
A constexpr SDM must be initialised in class with a constant expression.
C++17 no longer requires an initializer when the default constructor initialises every member. Also, constexpr SDMs are implicitly inline variables, which makes their declaration a definition (external definitions are now deprecated).
Other kinds of SDMs can have an initializer at their definition (which is in class if that SDM is declared inline).
Nothing has substantially changed between C++03 and C++11+ for code that is valid in both languages.
Note that for SDMs that are not inline, the in-class declaration is not a definition—regardless of whether an initializer is provided—and they must be defined if they are odr-used.
As of C++17, we can make your SDM inline, which makes its in-class declaration a definition:
class BufferedOutput
{
static inline long bytecount = 50;
};
Related
I want to have a static const char array in my class. GCC complained and told me I should use constexpr, although now it's telling me it's an undefined reference. If I make the array a non-member then it compiles. What is going on?
// .hpp
struct foo {
void bar();
static constexpr char baz[] = "quz";
};
// .cpp
void foo::bar() {
std::string str(baz); // undefined reference to baz
}
Add to your cpp file:
constexpr char foo::baz[];
Reason: You have to provide the definition of the static member as well as the declaration. The declaration and the initializer go inside the class definition, but the member definition has to be separate.
C++17 introduces inline variables
C++17 fixes this problem for constexpr static member variables requiring an out-of-line definition if it was odr-used. See the second half of this answer for pre-C++17 details.
Proposal P0386 Inline Variables introduces the ability to apply the inline specifier to variables. In particular to this case constexpr implies inline for static member variables. The proposal says:
The inline specifier can be applied to variables as well as to functions. A variable declared
inline has the same semantics as a function declared inline: it can be defined, identically, in
multiple translation units, must be defined in every translation unit in which it is odr-used, and
the behavior of the program is as if there is exactly one variable.
and modified [basic.def]p2:
A declaration is a definition unless
...
it declares a static data member outside a class definition and the variable was defined within the class with the constexpr specifier (this usage is deprecated; see [depr.static_constexpr]),
...
and add [depr.static_constexpr]:
For compatibility with prior C++ International Standards, a constexpr
static data member may be redundantly redeclared outside the class
with no initializer. This usage is deprecated. [ Example:
struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
};
constexpr int A::n; // redundant declaration (definition in C++ 2014)
— end example ]
C++14 and earlier
In C++03, we were only allowed to provide in-class initializers for const integrals or const enumeration types, in C++11 using constexpr this was extended to literal types.
In C++11, we do not need to provide a namespace scope definition for a static constexpr member if it is not odr-used, we can see this from the draft C++11 standard section 9.4.2 [class.static.data] which says (emphasis mine going forward):
[...]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.
So then the question becomes, is baz odr-used here:
std::string str(baz);
and the answer is yes, and so we require a namespace scope definition as well.
So how do we determine if a variable is odr-used? The original C++11 wording in section 3.2 [basic.def.odr] says:
An expression is potentially evaluated unless it is an unevaluated
operand (Clause 5) or a subexpression thereof. A variable whose name
appears as a potentially-evaluated expression is odr-used unless
it is an object that satisfies the requirements for appearing in a
constant expression (5.19) and the lvalue-to-rvalue conversion
(4.1) is immediately applied.
So baz does yield a constant expression but the lvalue-to-rvalue conversion is not immediately applied since it is not applicable due to baz being an array. This is covered in section 4.1 [conv.lval] which says :
A glvalue (3.10) of a non-function, non-array type T can be
converted to a prvalue.53 [...]
What is applied in the array-to-pointer conversion.
This wording of [basic.def.odr] was changed due to Defect Report 712 since some cases were not covered by this wording but these changes do not change the results for this case.
This is really a flaw in C++11 - as others have explained, in C++11 a static constexpr member variable, unlike every other kind of constexpr global variable, has external linkage, thus must be explicitly defined somewhere.
It's also worth noting that you can often in practice get away with static constexpr member variables without definitions when compiling with optimization, since they can end up inlined in all uses, but if you compile without optimization often your program will fail to link. This makes this a very common hidden trap - your program compiles fine with optimization, but as soon as you turn off optimization (perhaps for debugging), it fails to link.
Good news though - this flaw is fixed in C++17! The approach is a bit convoluted though: in C++17, static constexpr member variables are implicitly inline. Having inline applied to variables is a new concept in C++17, but it effectively means that they do not need an explicit definition anywhere.
My workaround for the external linkage of static members is to use constexpr reference member getters (which doesn't run into the problem #gnzlbg raised as a comment to the answer from #deddebme).
This idiom is important to me because I loathe having multiple .cpp files in my projects, and try to limit the number to one, which consists of nothing but #includes and a main() function.
// foo.hpp
struct foo {
static constexpr auto& baz() { return "quz"; }
};
// some.cpp
auto sz = sizeof(foo::baz()); // sz == 4
auto& foo_baz = foo::baz(); // note auto& not auto
auto sz2 = sizeof(foo_baz); // 4
auto name = typeid(foo_baz).name(); // something like 'char const[4]'
Isn't the more elegant solution be changing the char[] into:
static constexpr char * baz = "quz";
This way we can have the definition/declaration/initializer in 1 line of code.
On my environment, gcc vesion is 5.4.0. Adding "-O2" can fix this compilation error. It seems gcc can handle this case when asking for optimization.
I want to have a static const char array in my class. GCC complained and told me I should use constexpr, although now it's telling me it's an undefined reference. If I make the array a non-member then it compiles. What is going on?
// .hpp
struct foo {
void bar();
static constexpr char baz[] = "quz";
};
// .cpp
void foo::bar() {
std::string str(baz); // undefined reference to baz
}
Add to your cpp file:
constexpr char foo::baz[];
Reason: You have to provide the definition of the static member as well as the declaration. The declaration and the initializer go inside the class definition, but the member definition has to be separate.
C++17 introduces inline variables
C++17 fixes this problem for constexpr static member variables requiring an out-of-line definition if it was odr-used. See the second half of this answer for pre-C++17 details.
Proposal P0386 Inline Variables introduces the ability to apply the inline specifier to variables. In particular to this case constexpr implies inline for static member variables. The proposal says:
The inline specifier can be applied to variables as well as to functions. A variable declared
inline has the same semantics as a function declared inline: it can be defined, identically, in
multiple translation units, must be defined in every translation unit in which it is odr-used, and
the behavior of the program is as if there is exactly one variable.
and modified [basic.def]p2:
A declaration is a definition unless
...
it declares a static data member outside a class definition and the variable was defined within the class with the constexpr specifier (this usage is deprecated; see [depr.static_constexpr]),
...
and add [depr.static_constexpr]:
For compatibility with prior C++ International Standards, a constexpr
static data member may be redundantly redeclared outside the class
with no initializer. This usage is deprecated. [ Example:
struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
};
constexpr int A::n; // redundant declaration (definition in C++ 2014)
— end example ]
C++14 and earlier
In C++03, we were only allowed to provide in-class initializers for const integrals or const enumeration types, in C++11 using constexpr this was extended to literal types.
In C++11, we do not need to provide a namespace scope definition for a static constexpr member if it is not odr-used, we can see this from the draft C++11 standard section 9.4.2 [class.static.data] which says (emphasis mine going forward):
[...]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.
So then the question becomes, is baz odr-used here:
std::string str(baz);
and the answer is yes, and so we require a namespace scope definition as well.
So how do we determine if a variable is odr-used? The original C++11 wording in section 3.2 [basic.def.odr] says:
An expression is potentially evaluated unless it is an unevaluated
operand (Clause 5) or a subexpression thereof. A variable whose name
appears as a potentially-evaluated expression is odr-used unless
it is an object that satisfies the requirements for appearing in a
constant expression (5.19) and the lvalue-to-rvalue conversion
(4.1) is immediately applied.
So baz does yield a constant expression but the lvalue-to-rvalue conversion is not immediately applied since it is not applicable due to baz being an array. This is covered in section 4.1 [conv.lval] which says :
A glvalue (3.10) of a non-function, non-array type T can be
converted to a prvalue.53 [...]
What is applied in the array-to-pointer conversion.
This wording of [basic.def.odr] was changed due to Defect Report 712 since some cases were not covered by this wording but these changes do not change the results for this case.
This is really a flaw in C++11 - as others have explained, in C++11 a static constexpr member variable, unlike every other kind of constexpr global variable, has external linkage, thus must be explicitly defined somewhere.
It's also worth noting that you can often in practice get away with static constexpr member variables without definitions when compiling with optimization, since they can end up inlined in all uses, but if you compile without optimization often your program will fail to link. This makes this a very common hidden trap - your program compiles fine with optimization, but as soon as you turn off optimization (perhaps for debugging), it fails to link.
Good news though - this flaw is fixed in C++17! The approach is a bit convoluted though: in C++17, static constexpr member variables are implicitly inline. Having inline applied to variables is a new concept in C++17, but it effectively means that they do not need an explicit definition anywhere.
My workaround for the external linkage of static members is to use constexpr reference member getters (which doesn't run into the problem #gnzlbg raised as a comment to the answer from #deddebme).
This idiom is important to me because I loathe having multiple .cpp files in my projects, and try to limit the number to one, which consists of nothing but #includes and a main() function.
// foo.hpp
struct foo {
static constexpr auto& baz() { return "quz"; }
};
// some.cpp
auto sz = sizeof(foo::baz()); // sz == 4
auto& foo_baz = foo::baz(); // note auto& not auto
auto sz2 = sizeof(foo_baz); // 4
auto name = typeid(foo_baz).name(); // something like 'char const[4]'
Isn't the more elegant solution be changing the char[] into:
static constexpr char * baz = "quz";
This way we can have the definition/declaration/initializer in 1 line of code.
On my environment, gcc vesion is 5.4.0. Adding "-O2" can fix this compilation error. It seems gcc can handle this case when asking for optimization.
I would like to initialize my private static member "pi" in the definition of the class in order to tidy my code better having the initializations in the .cpp files.
When I try this error shows up: "Declaration of constexpr static data member 'pi' requires an initializer"
I'm using CLion 2018.3.4 and C++ 11.
I tried to work around and the only solution is to initialize the member in the declaration.
Other answers on Stack Overflow provided me a better knowledge but didn't answer my question.
// .h file
class Shape {
public:
virtual void getArea();
private:
static constexpr float pi; // the error shows up here
};
// .cpp file
#include "Shape.h"
const float Shape::pi = 3.14; //here I don't exactly know why it does not require constexpr, but it's fine even with and without const
// what I think this is equivalent to (in .h file)
static constexpr float pi = 3.14;
I was expecting this to work like I was assigning "3.14" in the declaration.
I'm not defining a constructor, in that case I know it wouldn't work because the c'tor is meant to initialize an instance of the class while a static member is supposed to be already initialized being a global element in the "Shape" namespace.
What I suppose it is happening is that the linker tries to initialize the member in the header because it has to be done in the pre-processing phase while the .cpp file is used later.
With static constexpr members you cannot leave off the initializer in the class definition. A constexpr variable must be initialized when declared because it can be used after it is declared in a constant expression. This is detailed in [class.static.data]/3 of the C++11 standard
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 ([expr.const]). 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 ([basic.def.odr]) in the program and the namespace scope definition shall not contain an initializer.
emphasis mine
So, with that, your code needs to be
// .h file
class Shape {
public:
virtual void getArea();
private:
static constexpr float pi = 3.14; // we initialize here so it can be used.
};
// .cpp file
constexpr float Shape::pi; // we define here so it can be odr-used
Do note that this has changed in C++17. With the introduction of inline variables static constexpr member variables no longer need to be defined outside of the class. The compiler will handle it for you and ensure only a single defentition of the object exists. You can still define the member if you want, but that ability is deprecated and will most likely be removed in a future standard revision. The new text for [class.static.data]/3 is
If a non-volatile non-inline 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 ([expr.const]). The member shall still be defined in a namespace scope if it is odr-used ([basic.def.odr]) in the program and the namespace scope definition shall not contain an initializer. An inline static data member may be defined in the class definition and may specify a brace-or-equal-initializer. If the member is declared with the constexpr specifier, it may be redeclared in namespace scope with no initializer (this usage is deprecated; see [depr.static.constexpr]). Declarations of other static data members shall not specify a brace-or-equal-initializer.
emphasis mine
and [dcl.constexpr]/1 says that a static constexpr variable is implicitly inline
The constexpr specifier shall be applied only to the definition of a variable or variable template or the declaration of a function or function template. The consteval specifier shall be applied only to the declaration of a function or function template. A function or static data member declared with the constexpr or consteval specifier is implicitly an inline function or variable ([dcl.inline]). If any declaration of a function or function template has a constexpr or consteval specifier, then all its declarations shall contain the same specifier.
emphasis mine
The following fails to compile under both gcc and clang in c++14, but succeeds with c++1z:
struct Cls {
static constexpr int N = 0;
};
constexpr int Cls::N;
constexpr int Cls::N;
The C++14 error is predictable: redefinition of ‘constexpr const int Cls::N’
What changed to make this legal? I found:
n4659 10.1.5 [dcl.constexpr]
A function or static data member declared with the constexpr
specifier is implicitly an inline function or variable
So I thought it might have to do with inline variables, but the following fails for c++1z under both compilers
struct Cls {
static inline const int N = 0;
};
inline const int Cls::N; // note, only one definition here
Before C++17, you needed to re-declare all static variables outside the class in exactly one translation unit (typically each translation unit is a .cpp file and vice versa, but this isn’t required). As you pointed out, C++17 introduces inline class member variables, and static constexpr variables automatically qualify. You are not allowed to redeclare inline variables outside the class, as you saw in your second example, but an exception was made for constexpr because previously you were allowed (and in fact required) to do so, but the syntax is deprecated.
In [class.static.data]p2, it allows that syntax for non-inline members (“The declaration of a non-inline static data member in its class definition is not a definition and may be of
an incomplete type other than cv void. The definition for a static data member that is not defined inline
in the class definition shall appear in a namespace scope enclosing the member’s class definition.”)
In the next paragraph, the standard allows constexpr outside-of-class declarations and requires them for non-constexpr data (emphasis added):
If a non-volatile non-inline 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 (8.20). The member shall still be defined in a
namespace scope if it is odr-used (6.2) in the program and the
namespace scope definition shall not contain an initializer. An inline
static data member may be defined in the class definition
and may specify a brace-or-equal-initializer. If the member is
declared with the constexpr specifier, it may be redeclared in
namespace scope with no initializer (this usage is deprecated; see
D.1). Declarations of other static data members shall not specify a
brace-or-equal-initializer.
And here’s the deprecation note, D.1 Redeclaration of static constexpr data members [depr.static_constexpr]:
For compatibility with prior C++ International Standards, a constexpr static data member may be redundantly redeclared outside the
class with no initializer. This usage is deprecated. [ Example:
struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
};
constexpr int A::n; // redundant declaration (definition in C++ 2014)
— end example ]
What is the right way to initialize static data members in C++? I'm also interested in how it has changed from C++98, to C++11 to C++14.
Here is an example:
// bufferedOutput.h
class BufferedOutput
{
// Static member declaration.
static long bytecount;
};
// bufferedOutput.cpp
long BufferedOutput::bytecount = 50;
Are there other ways to initialize static data members?
The rules have always been as follows:
A const static data member (SDM) of integral or enumeration type can be initialised in class with a constant expression.
A constexpr SDM must be initialised in class with a constant expression.
C++17 no longer requires an initializer when the default constructor initialises every member. Also, constexpr SDMs are implicitly inline variables, which makes their declaration a definition (external definitions are now deprecated).
Other kinds of SDMs can have an initializer at their definition (which is in class if that SDM is declared inline).
Nothing has substantially changed between C++03 and C++11+ for code that is valid in both languages.
Note that for SDMs that are not inline, the in-class declaration is not a definition—regardless of whether an initializer is provided—and they must be defined if they are odr-used.
As of C++17, we can make your SDM inline, which makes its in-class declaration a definition:
class BufferedOutput
{
static inline long bytecount = 50;
};