131
votes

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

class A
{
    static const int a = 3;
    static int b = 3;
    static const int c[2] = { 1, 2 };
    static int d[2] = { 1, 2 };
};

int main()
{
    A a;

    return 0;
}

the compiler issues following errors:

g++ main.cpp
main.cpp:4:17: error: ISO C++ forbids in-class initialization of non-const static member ‘b’
main.cpp:5:26: error: a brace-enclosed initializer is not allowed here before ‘{’ token
main.cpp:5:33: error: invalid in-class initialization of static data member of non-integral type ‘const int [2]’
main.cpp:6:20: error: a brace-enclosed initializer is not allowed here before ‘{’ token
main.cpp:6:27: error: invalid in-class initialization of static data member of non-integral type ‘int [2]’

I have two questions:

  1. Why can't I initialize static data members in class?
  2. Why can't I initialize static arrays in class, even the const array?
5
I think the main reason is that it is tricky to get right. In principle, you could probably do what you are talking about, but there would be some weird side effects. Like if your array example was allowed, then you may be able to get the value of A::c[0], but not be able to pass A::c to a function since that would require an address, and compile-time constants don't have an address. C++11 has enabled some of this by the use of constexpr.Vaughn Cato
Great question and makred answer. Link that helped me: msdn.microsoft.com/en-us/library/0e5kx78b.aspxETFovac

5 Answers

157
votes

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.

7
votes

This seems a relict from the old days of simple linkers. You can use static variables in static methods as workaround:

// header.hxx
#include <vector>

class Class {
public:
    static std::vector<int> & replacement_for_initialized_static_non_const_variable() {
        static std::vector<int> Static {42, 0, 1900, 1998};
        return Static;
    }
};

int compilation_unit_a();

and

// compilation_unit_a.cxx
#include "header.hxx"

int compilation_unit_a() {  
    return Class::replacement_for_initialized_static_non_const_variable()[1]++;
}

and

// main.cxx
#include "header.hxx"

#include <iostream>

int main() {
    std::cout
    << compilation_unit_a()
    << Class::replacement_for_initialized_static_non_const_variable()[1]++
    << compilation_unit_a()
    << Class::replacement_for_initialized_static_non_const_variable()[1]++
    << std::endl;
}

build:

g++ -std=gnu++0x -save-temps=obj -c compilation_unit_a.cxx 
g++ -std=gnu++0x -o main main.cxx compilation_unit_a.o

run:

./main

The fact that this works (consistently, even if the class definition is included in different compilation units), shows that the linker today (gcc 4.9.2) is actually smart enough.

Funny: Prints 0123 on arm and 3210 on x86.

2
votes

It's because there can only be one definition of A::a that all the translation units use.

If you performed static int a = 3; in a class in a header included in all a translation units then you'd get multiple definitions. Therefore, non out-of-line definition of a static is forcibly made a compiler error.

Using static inline or static const remedies this. static inline only concretises the symbol if it is used in the translation unit and ensures the linker only selects and leaves one copy if it's defined in multiple translation units due to it being in a comdat group. const at file scope makes the compiler never emit a symbol because it's always substituted immediately in the code unless extern is used, which is not permitted in a class.

One thing to note is static inline int b; is treated as a definition whereas static const int b or static const A b; are still treated as a declaration and must be defined out-of-line if you don't define it inside the class. Interestingly static constexpr A b; is treated as a definition, whereas static constexpr int b; is an error and must have an initialiser (this is because they now become definitions and like any const/constexpr definition at file scope, they require an initialiser which an int doesn't have but a class type does because it has an implicit = A() when it is a definition -- clang allows this but gcc requires you to explicitly initialise or it is an error. This is not a problem with inline instead). static const A b = A(); is not allowed and must be constexpr or inline in order to permit an initialiser for a static object with class type i.e to make a static member of class type more than a declaration. So yes in certain situations A a; is not the same as explicitly initialising A a = A(); (the former can be a declaration but if only a declaration is allowed for that type then the latter is an error. The latter can only be used on a definition. constexpr makes it a definition). If you use constexpr and specify a default constructor then the constructor will need to be constexpr

#include<iostream>

struct A
{
    int b =2;
    mutable int c = 3; //if this member is included in the class then const A will have a full .data symbol emitted for it on -O0 and so will B because it contains A.
    static const int a = 3;
};

struct B {
    A b;
    static constexpr A c; //needs to be constexpr or inline and doesn't emit a symbol for A a mutable member on any optimisation level
};

const A a;
const B b;

int main()
{
    std::cout << a.b << b.b.b;
    return 0;
}

A static member is an outright file scope declaration extern int A::a; (which can only be made in the class and out of line definitions must refer to a static member in a class and must be definitions and cannot contain extern) whereas a non-static member is part of the complete type definition of a class and have the same rules as file scope declarations without extern. They are implicitly definitions. So int i[]; int i[5]; is a redefinition whereas static int i[]; int A::i[5]; isn't but unlike 2 externs, the compiler will still detect a duplicate member if you do static int i[]; static int i[5]; in the class.

1
votes

I think it's to prevent you from mixing declarations and definitions. (Think about the problems that could occur if you include the file in multiple places.)

-3
votes

static variables are specific to a class . Constructors initialize attributes ESPECIALY for an instance.