Why Can't I Initialize Non-Const Static Member or Static Array in Class

Why can't I initialize non-const static member or static array in class?

Why I can't initialize static data members in class?

The C++ standard allows only static constant integral or enumeration types to be initialized inside the class. This is the reason a is allowed to be initialized while others are not.

Reference:

C++03 9.4.2 Static data members

§4

If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be an integral constant expression (5.19). In that case, the member can appear in integral constant expressions. The member shall still be defined in a namespace scope if it is used in the program and the namespace scope definition shall not contain an initializer.

What are integral types?

C++03 3.9.1 Fundamental types

§7

Types bool, char, wchar_t, and the signed and unsigned integer types are collectively called integral types.43) A synonym for integral type is integer type.

Footnote:

43) Therefore, enumerations (7.2) are not integral; however, enumerations can be promoted to int, unsigned int, long, or unsigned long, as specified in 4.5.

Workaround:

You could use the enum trick to initialize an array inside your class definition.

class A 
{
static const int a = 3;
enum { arrsize = 2 };

static const int c[arrsize] = { 1, 2 };

};

Why does the Standard does not allow this?

Bjarne explains this aptly here:

A class is typically declared in a header file and a header file is typically included into many translation units. However, to avoid complicated linker rules, C++ requires that every object has a unique definition. That rule would be broken if C++ allowed in-class definition of entities that needed to be stored in memory as objects.

Why are only static const integral types & enums allowed In-class Initialization?

The answer is hidden in Bjarne's quote read it closely,

"C++ requires that every object has a unique definition. That rule would be broken if C++ allowed in-class definition of entities that needed to be stored in memory as objects."

Note that only static const integers can be treated as compile time constants. The compiler knows that the integer value will not change anytime and hence it can apply its own magic and apply optimizations, the compiler simply inlines such class members i.e, they are not stored in memory anymore, As the need of being stored in memory is removed, it gives such variables the exception to rule mentioned by Bjarne.

It is noteworthy to note here that even if static const integral values can have In-Class Initialization, taking address of such variables is not allowed. One can take the address of a static member if (and only if) it has an out-of-class definition.This further validates the reasoning above.

enums are allowed this because values of an enumerated type can be used where ints are expected.see citation above


How does this change in C++11?

C++11 relaxes the restriction to certain extent.

C++11 9.4.2 Static data members

§3

If a static data member is of const literal 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. 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 used in the program and the namespace scope definition shall not contain an initializer.

Also, C++11 will allow(§12.6.2.8) a non-static data member to be initialized where it is declared(in its class). This will mean much easy user semantics.

Note that these features have not yet been implemented in latest gcc 4.7, So you might still get compilation errors.

error: non-const static data member must be initialized out of line

Bjarne Stroustrup explains this here:

A class is typically declared in a header file and a header file is
typically included into many translation units. However, to avoid
complicated linker rules, C++ requires that every object has a unique
definition. That rule would be broken if C++ allowed in-class
definition of entities that needed to be stored in memory as objects.

As said by Stroustrup, every class needs a unique definition. Now, as we know static members are associated directly with their class. Now consider the two cases:

  1. The static member is also constant, then its initialization is allowed inline because the compiler can make its own optimisations and treat this member as a compile-time constant because it is guaranteed that its value will never change. So, as the value of this member is fixed, the definition of the class with which this member is associated is also fixed. So, the initialization is allowed inline.

  2. The static member is not constant. Then its value can change later on during the execution of the program. So, the compiler can not make compile-time optimisations on this member. Hence, to prevent the complications that may arise while trying to initialize such a member when the class is loaded, inline initialisation of such members is not allowed.

PS: When I heard about this concept the very first time, I was also confused because it is not in accordance with the principle of orthogonality that is a feature desired by programmers. The principle of orthogonality will state that since we can combine int and static; and int and const, we should be able to write static const int and static int in a similar fashion. But this case here is an example of a situation where the developer of a language has to give up orthogonality for the users of the language in exchange of the simplicity of the compilation process.

Have a look at the concept of orthogonality here

Why can one initialize non-const and static const member variables but not static member variables?

It has to do with where the data is stored. Here's a breakdown:

  • int: member variable, stored wherever the class instance is stored
  • const int: same as int
  • static const int: doesn't need to be stored, it can simply be "inlined" where used
  • static int: this must have a single storage location in the program...where?

Since the static int is mutable, it must be stored in an actual location somewhere, so that one part of the program can modify it and another part can see that modification. But it can't be stored in a class instance, so it must be more like a global variable. So why not just make it a global variable? Well, class declarations are usually in header files, and a header file may be #included in multiple translation units (.cpp files). So effectively the header file says "there is an int...somewhere." But the storage needs to be put into the corresponding .cpp file (like a global variable).

In the end, this is not really about initialization, but rather the storage. You could leave off the initializer and you'd still not have a valid program until you add this to your .cpp file:

int A::d; // initialize if you want to, default is zero

Without this, references to the static int will be undefined and linking will fail.

Why do non-constant static variables need to be initialized outside the class?

Essentially it's because x exists independently of the number of instances of A that are created.

So storage for x needs to be defined somewhere - you can't rely on an instance of A to do that, and that's what

A::x = 0;

in exactly one translation unit, does.

error: ISO C++ forbids in-class initialization of non-const static member

The initialization of the static member counter must not be in the header file.

Change the line in the header file to

static int counter;

And add the following line to your employee.cpp:

int Employee::counter = 0;

Reason is that putting such an initialization in the header file would duplicate the initialization code in every place where the header is included.

Why can Static data member not be initialized in-class in c++11?

Non-static data member in-class initialization

First of, this is completely different than static member initialization.

In-class member initialization is just a syntactic sugar that transforms to constructor initialization list.

E.g.

struct X
{
int a_ = 24;
int b_ = 11;
int c_;

X(int c) : c_{c}
{
}

X(int b, int c) : b_{b}, c_{c}
{
}
};

Pretty much is equivalent to:

struct X
{
int a_;
int b_;
int c_;

X(int c) : a_{24}, b{11}, c_{c}
{
}

X(int b, int c) : a{24}, b_{b}, c_{c}
{
}
};

Just syntactic sugar. Nothing that couldn't been done previous to C++11 with more verbose code.

Static data member in-class initialization

Things are more complicated here because there has to be just 1 symbol for the static data member. You should read about ODR (One Definition Rule).

Let's start with const static data member. You might be surprised that initialization is allowed only from compile time constant expressions:

auto foo() { return 24; }
constexpr auto bar() { return 24 };

struct X
{
static const int a = foo(); // Error
static const int b = bar(); // Ok
};

The actual rule (well not a rule per se, but a reasoning if you will) is more general (for both const and non-const static data members): static data member initialization, if in line, must be a compile time expression. This effectively means that the only static data member in line initialization allowed is for const static data members with constexpr initialization.

Now let's see the reasoning behind that: if you have an in line initialization that would make it a definition and this means that every compilation unit where the definition of X appears will have a X::a symbol. And every such compilation unit will need to initialize the static member. In our example foo would be called for each compilation unit that includes directly or indirectly the header with the definition of X.

The first problem with this is that it's unexpected. The number of calls to foo will depend on the number of compilation units that have included X, even if you wrote a single call to foo for a single initialization of a single static member.

There is a more serious problem though: foo not being a constexpr function nothing prevents foo from returning different results on each invocation. So you will end up with a bunch of X::a symbols which should be under ODR but each of them initialized with different values.

If you are still not convinces then there is the 3rd problem: having multiple definition of X::a would simply be a violation of ODR. So... the previous two problems are just some of the motivations for why ODR exists.

Forcing an out-of line definition for X::a is the only way which allows correct definition and initialization of X::a: in a single compilation unit. You can still mess up and write the out of line definition and initialization in a header, but with an in line initialization you definitely have multiple initializations.

As n.m. showed since C++17 you have inline data members and here we are allowed in-class initialization:

struct X
{
static inline int i = foo();
};

Now we can understand why: with inline the compiler will chose just one definition of X::i (from one compilation unit) and so you just have one evaluation of the initialization expression that is chosen from one compilation unit. Note that it is still your duty to respect ODR.

Why can't I initialize a static variable with a non literal value?

C requires it.

From the C Standard:

(C99, 6.7.8p4) "All the expressions in an initializer for an object that has static storage duration shall be constant expressions or string literals."

Note that the const qualifier does not mean constant but rather read-only. A const object is not a constant in C.

The reason a static object cannot be initialized by a non constant value is related to the fact that the initialization of a static object is done "prior to program startup" (C99, 6.2.4p3).



Related Topics



Leave a reply



Submit