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

6

151

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?
Floatage answered 11/3, 2012 at 17:1 Comment(2)
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.Week
Great question and makred answer. Link that helped me: msdn.microsoft.com/en-us/library/0e5kx78b.aspxValance
F
174

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.

Frady answered 11/3, 2012 at 17:16 Comment(13)
But I wander why the standard allow only static constant integer or enumeration types to be initialized inside the class? Is there any disadvantages if other types can also be initialized like this? @Als, thank you.Floatage
Things are different in c++11. Answer could use updating.Awoke
@bames53: Done. Hope that helps.Frady
This doesn't seem to be true : "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," Are you sure they're necessarily not stored in memory? What if I provide definitions for the members? What would &member return?Merited
@Nawaz: "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". Copy pasted from the answer.Frady
@Als: Yeah. That is what my question is. So why does C++ allow in-class initialization for integral types only, is not answered correctly by your answer. Think of why it doesn't allow initialization for static const char* member?Merited
@Nawaz: Because C++03 only allowed constant-initializer for static and const integral and const enumeration type and no other type, C++11 extends this to an const literal type which relaxes the norms for In-Class Initialization.The limitation in C++03 was perhaps a oversight which warranted a change and hence was corrected in C++11, if there are any traditional tactical reasons for the change I am not aware of them.If you are aware of any feel free to share them.Frady
@Als: Now that seems to be the correct answer, or at least I cannot argue against it. Howeever, the rationale you provided in your answer (which I've quoted) is untenable.Merited
The "Workaround" par you mentioned is not working with g++.Complexion
So just to confirm... What we're saying is: (1) It's that way because the C++03 language standard says it's that way; (2) there's no underlying logical reason it should be that way; (3) it's fixed in C++11.Restorative
But why is it allowed to use inclass initialization only for integral type: const static int x = 10; // C++11: valid const static string state = "init"; // C++11: invalidStarfish
"Why I can't initialize static data members in class?" >>> Thought that's an "assignment" instead of "initialization", no?Oleary
Yet another example where C++ is less expressive and difficult to program in the sake of being compiler friendly..Leptosome
A
8

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.

Antoineantoinetta answered 24/7, 2015 at 10:26 Comment(0)
G
6

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.

Grouch answered 9/6, 2020 at 16:58 Comment(3)
This is a very nice answer. Where can I find relevant rules in the standards, if they're at all included? I know static inline being a definition is in [class.static.general]/4 but what about things like static constexpr being a definition as well?Auxiliaries
Also, "const at file scope makes the compiler never emit a symbol [...]" is it const or static?Auxiliaries
@Auxiliaries I totally forgot about this answer – that I had already written an answer on this matter and it seems wrong to me. I haven't read it properly but it reads incorrectly at a glance (see this for my most updated answer). I will change it.Grouch
U
1

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.)

Unrivalled answered 11/3, 2012 at 17:19 Comment(0)
D
0

Starting with C++17 the simplest solution is to inline the variables:

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

There is something called the One Definition Rule (ODR) in C++ which prohibits multiple declarations of the same functions or variables. Because you created these variables as static, you intended to create one instance of this variable. However, by including this file in multiple translation units, you created one instance of this variable per translation unit, therefore your linker detected a violation of the ODR.

Note: one c/cpp file usually results in one translation unit.

Despite common believe, the inline keyword is not just for optimization in C++. When applied to a function, it tells the linker that there may be multiple definitions of this function, but all of them are identical. Similar to your case, functions defined in a header file must usually be marked as inline to avoid linker issues.

In essence, inlining means that a function defined in a header can be included in multiple compilation units without linker issues. Because all of these definitions must be identical, the linker can just pick one of them and ignore the others.

Since C++17, this behaviour also works for static variables.

The reason you could define your variable a without the inline keyword is simply compiler optimization: static const Integral types and enums are not stored the way you expect them to be, rather they are treated more similar to #defines. Their value is simply put in place wherever the variables are used.

Note:

A static data member declared constexpr on its first declaration is implicitly an inline variable.

Dees answered 30/4 at 8:35 Comment(0)
F
-4

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

Ferrol answered 23/3, 2015 at 19:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.