4
votes

Possible Duplicate
Why can't I have a non-integral static const member in a class?

struct Example
{
    static const int One = 1000; // Legal
    static const short Two = 2000; // Illegal
    static const float Three = 2000.0f; // Illegal
    static const double Four = 3000.0; // Illegal
    static const string Five = "Hello"; // Illegal
};

Is there any reason for which #2, #3, #4 and #5 are illegal?

I think I know the reason for #5: the compiler needs a "real" string object (since it's not a built in type) and cannot mindlessy replace Five with "Hello" as if it was #define Five "Hello". But if that's the case, can't the compiler leave an hint in the .obj files and tell the linker to automatically create one instance of string Five somewhere?

For #3 and #4 and especially #2 (lol!)... I can't really see any possible reason! Floats and doubles are built-in types, just as int is! And short is just a (possibly) shorter integer.


EDIT: I'm using Visual Studio 2008 to compile it. I thought all compilers behaved the same in this case, but apparently g++ compiles that fine (except #5). The errors VS gives for that snippets are:

    error C2864: 'Example::Two' : only static const integral data members can be initialized within a class
    error C2864: 'Example::Three' : only static const integral data members can be initialized within a class
    error C2864: 'Example::Four' : only static const integral data members can be initialized within a class
    error C2864: 'Example::Five' : only static const integral data members can be initialized within a class
8
have you tried it on different compilers ? If so which one's ? and what where the error messages ?Hassan Syed
What compiler are you using? Compiles fine for me with G++ (except the string).schnaader
VS2008 (edited the main question)Thomas Bonini
@schnaader: you should have used -pedantic.Steve Jessop

8 Answers

7
votes

The int and the short are legal, and if your compiler doesn't allow them then your compiler is bust:

9.4.2/4: ... If the 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.

I believe that the reason that floats and doubles aren't treated specially as constants in the C++ standard, in the way that integral types are, is that the C++ standard is wary that the arithmetic operations on float and double could be subtly different on the compiling machine, than they are on the machine that executes the code. For the compiler to evaluate a constant expression like (a + b), it needs to get the same answer that the runtime would get.

This isn't so much of an issue with ints - you can emulate integer arithmetic relatively cheaply if it differs. But for the compiler to emulate floating-point hardware on the target device might be very difficult. It might even be impossible, if there are different versions of the chip and the compiler doesn't know which the code will run on. And that's even before you start messing with the IEEE rounding mode. So the standard avoided requiring it, so that it didn't have to define when and how compile-time evaluation can differ from runtime evaluation.

As Brian mentions, C++0x is going to address this with constexpr. If I'm right about the original motivation, then presumably 10 years has been long enough to work through the difficulties in specifying this stuff.

4
votes

Both Example::One and Example::Two should compile for you, and they do indeed compile for me in the same environment you stated (VS 2008).

I don't believe Example::Three, and Example::Four should compile at all in standard C++, but I think there is a gcc extension that allows it. Example::Five should not compile.

You can initialize them like so after the struct declaration, typically in your source file:

const float Example::Three = 2000.0f;
const double Example::Four = 3000.0;
const string Example::Five = "Hello";

This is the most portable way to do it, and the way I would recommend doing it even if your compiler allows you to define Example::Three and Example::Four in your declaration.

Another option would be to simply return the value from a static function of the same type.

struct Example
{
    //...
    static double Four() { return  = 3000.0; }
    //...
};

This answer discusses a possible reason as well.
This answer discusses how the upcoming C++ standard will help via constexpr

1
votes

#1 and 2 are compliant with the standard. Unfortunately, some compilers simply don't conform. That's why, for example, the Boost designers had to introduce annoying macros like BOOST_STATIC_CONSTANT to generate portable libraries. If you don't want to define the constant in a .cpp file, a portable workaround is to use an enum. Although, obviously in that case you have no guarantee about the type, and you can't use floats.

1
votes

In C++98, only static const members of integral types can be initialized in-class, and the initializer has to be a constant expression. These restrictions ensure that we can do the initialization at compile-time.

See In-class member initializers.

§9.4.2 Static data members

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.

0
votes

Under VS2008 I get the following error:

1>.\Weapon Identification SystemDlg.cpp(223) : error C2864: 'Example::Three' : only static const integral data members can be initialized within a class
1>.\Weapon Identification SystemDlg.cpp(224) : error C2864: 'Example::Four' : only static const integral data members can be initialized within a class
1>.\Weapon Identification SystemDlg.cpp(225) : error C2864: 'Example::Five' : only static const integral data members can be initialized within a class

It sucks but I guess you just have to NOT do it if your compiler refuses too ... I'm not aware of this being a spec thing but im sure someone will correct me ...

0
votes

Re the floating point initializers, the C++98 spec has this to say (5.19):

Floating literals can appear only if they are cast to integral or enumeration types.

0
votes

As others have found, the C++ standard forbids initializing a static const member with a floating point value.

At least as I understand it, there's a fairly simple reason for this. There's a feeling (at least partially justified) that an implementation should be allowed to adjust the floating point precision dynamically, so it might not be until runtime that the implementation knows the exact floating point value that would/will be produced from a particular floating point literal. In fact, it's even possible that this could change during execution.

This capability does exist in real hardware. Just for example, the Intel x86 has a couple of bits in the floating point control register that control the accuracy of floating point calculations. By default, calculations are done on the 80-bit long double type, and only rounded to something like a 64-bit double or 32-bit float upon request. These bits in the register can be modified during execution, so (for example) "1.23" in one place could initialize a variable to one value, while "1.23" in another part of the program (after the precision had been adjusted) could result in a (slightly) different value.

At least as far as I know, this remains a theoretical possibility, at least on most typical machines. Although the Intel hardware allows dynamic adjustment of FP precision, I don't know of any compiler (not even Intel's) that attempts to take such an adjustment into account when translating FP literals (though Intel's compiler does at least support an 80-bit long double type).

0
votes

As others have pointed out, your compiler is broken in some cases. But I have never really understood the reason why it is not allowed for floating-point types, other than "The standard says so". There appears to be no good technical reason.