9

If I were to do this

class Gone
{
    public:
    static const int a = 3;
}

it works but if do

class Gone
{
    public:
    static int a = 3;
}

it gives a compile error. Now I know why the second one doesn't work, I just don't know why the first one does.

Thanks in advance.

4
  • possible duplicate of Defining static members in C++
    – Uku Loskit
    Commented Feb 9, 2012 at 22:38
  • This might help: stackoverflow.com/questions/370283/…
    – user500944
    Commented Feb 9, 2012 at 22:40
  • @Uku Loskit They didn't really answer the second part, I still don't understand the logic behind it.
    – rubixibuc
    Commented Feb 9, 2012 at 22:44
  • C++17 allows inline initialization of static data members (even for non-integer types): inline static int x[] = {1, 2, 3};. See en.cppreference.com/w/cpp/language/static#Static_data_members Commented Feb 14, 2018 at 22:45

5 Answers 5

7

This trick works only for constant compile-time expressions. Consider the following simple example:

#include <iostream>

class Foo {
public:
    static const int bar = 0;
};

int main()
{
    std::cout << Foo::bar << endl;
}

It works just fine, because compiler knows that Foo::bar is 0 and never changes. Thus, it optimizes the whole thing away.

However, the whole thing breaks once you take the address of that variable like this:

int main()
{
    std::cout << Foo::bar << " (" << &Foo::bar << ")" << std::endl;
}

Linker sends you to fix the program because compile-time constants don't have addresses.

Now, the second case in your example doesn't work simply because a non-constant variable cannot be a constant compile-time expression. Thus, you have to define it somewhere and cannot assign any values in initialization.

C++11, by the way, has constexpr. You can check Generalized constant expressions wiki (or C++11 standard :-)) for more info.

Also, be careful - with some toolchains you will never be able to link program as listed in your first example when optimizations are turned off, even if you never take an address of those variables. I think there is a BOOST_STATIC_CONSTANT macro in Boost to work around this problem (not sure if it works though because I reckon seeing linkage failures with some old gcc even with that macro).

2

The static const int declaration is legal because you're declaring a constant, not a variable. a doesn't exist as a variable - the compiler is free to optimize it out, replacing it with the declared value 3 anywhere a reference to Gone::a appears. C++ allows the static initialization in this restricted case where it's an integer constant.

You can find more details, including an ISO C++ standard citation here.

2

Initialization of variables has to be done at the point of definition, not the point of declaration in the general case. Inside the class brackets you only have a declaration and you need to provide a definition in a single translation unit*:

// can be in multiple translation units (i.e. a header included in different .cpp's)
struct test {
   static int x;    // declaration
   static double d; // declaration
};
// in a single translation unit in your program (i.e. a single .cpp file)
int test::x = 5;       // definition, can have initialization
double test::d = 5.0;  // definition

That being said, there is an exception for static integral constants (and only integral constants) where you can provide the value of the constant in the declaration. The reason for the exception is that it can be used as a compile-time constant (i.e. to define the size of an array), and that is only possible if the compiler sees the value of the constant in all translation units where it is needed.

struct test {
   static const int x = 5;  // declaration with initialization
};
const int test::x;          // definition, cannot have initialization

Going back to the original question:

  • Why is it not allowed for non-const integers?
  • because initialization happens in the definition and not declaration.
  • Why is it allowed for integral constants?
  • so that it can be used as a compile-time constant in all translation units

* The actual rules require the definition whenever the member attribute is used in the program. Now the definition of used is a bit tricky in C++03 as it might not be all that intuitive, for example the use of that constant as an rvalue does not constitute use according to the standard. In C++11 the term used has been replaced with odr-used in an attempt to avoid confusion.

3
  • Must you provide a definition for integral constants, or is the declaration enough?
    – rubixibuc
    Commented Feb 9, 2012 at 22:52
  • what is meant by member attribute? So when would I ever use it and need the definition? Would taking the address of it be using it?
    – rubixibuc
    Commented Feb 9, 2012 at 22:53
  • @rubixibuc: I should have used member variable or static member variable rather than member attribute. Regarding the first question: yes, you need to provide a definition if it is odr-used. When will it be odr-used? If you use it as an lvalue (which includes obtaining the address, or binding a reference to it). I would have to check if there are any other cases, but those are the most common ones (maybe only ones). Example: void f( const int & ); ... f( test::x ); requires test::x to be defined. Commented Feb 9, 2012 at 23:08
0

A static const is defined in the class definition since everybody that uses the code need to know the value at compile time, not link time. An ordinary static is actually only declared in the class definition, but defined once, in one translation unit.

0

I seem to recall that originally (ARM) it was not allowed, and we used to use enum to define constants in class declarations.

The const case was explicitly introduced so as to support availability of the value in headers for use in constant expressions, such as array sizes.

I think (and please comment if I have this wrong) that strictly you still need to define the value:

const int Gone::a;

to comply with the One Definition Rule. However, in practice, you might find that the compiler optimises away the need for an address for Gone::a and you get away without it.

If you take:

const int* b = &Gone::a;

then you might find you do need the definition.

See the standard, $9.4.2:

ISO 1998:

"4 If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constantinitializer which shall be an integral constant expression (5.19). In that case, the member can appear in integral constant expressions within its scope. 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."

Draft for c++11:

"3 If a static data member is of const effective literal type, its declaration in the class definition can specify a constant-initializer brace-or-equal-initializer with an initializer-clause that is an integral constant expression. A static data member of effective literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a constant-initializer brace-or-equal-initializer with an initializerclause that is an integral constant expression. In both these cases, the member may 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."

I am not sure entirely what this covers, but I think it means that we can now use the same idiom for floating point and possibly string literals.

Not the answer you're looking for? Browse other questions tagged or ask your own question.