views:

305

answers:

8

Possible Duplicate
http://stackoverflow.com/questions/370283/why-cant-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
+4  A: 

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

Brian R. Bondy
Yes; I am aware of this. I wanted to know though if there was a technical reason or really any reason at all for having to do this.
Andreas Bonini
Anyone know what the standard has to say about this ?
Hassan Syed
@Koper: I updated with a link
Brian R. Bondy
+1  A: 

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.

Gregory Pakosz
If you can initialize `int` at compile time you sure can initialize `short` under the same circumstances, can't you? So it doesn't explain the reason of this restriction
Andreas Bonini
`static const short s = 1;` works for me with MSVC2005
Gregory Pakosz
A: 

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

Goz
+1  A: 

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

Charles Salvia
+5  A: 

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.

Steve Jessop
Stupid standards. They could allow double.
Alexey Malistov
Well, it is kind of ironic that there are any decisions in the standard made to avoid burdening compiler-writers, and yet keyword `export` exists. But I think it's export which is the mistake, and an exception to the usual rule. It's good not to specify things until you're sure that the specification is consistent, and can be implemented everywhere that C++ wants to go.
Steve Jessop
"But for the compiler to emulate floating-point hardware on the target device might be very difficult."Surely this is true for the optmisation of any constant-expression, not just those which are inline constants. In addition, the fidelity of floating point is not guaranteed anyway since register and in-memory representations may differ, therefore the results you obtain are entirely dependent on compiler optimisation settings. Indeed it is possible for the same calculation to evaluate to slightly different values in the same program depending.
Robert Tuck
But aren't integral constant expressions "more constant" than constant expressions? The expectation is that they are better-behaved than just any old constant expression. Unfortunately I can't remember where I first read this idea that floats are not well-behaved enough to be like ICEs. If there's an authoritative rationale out there, by all means somebody quote it. I don't have a copy of *The Design and Evolution of C++*, or I'd look there first.
Steve Jessop
A: 

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.

Alex
A: 

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

Jerry Coffin
A: 

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.

Robert Tuck