75

I have a class which is essentially just holds a bunch of constant definitions used through my application. For some reason though, longs compile but floats do not:

class MY_CONSTS
{
public :
    static const long   LONG_CONST = 1;      // Compiles 
    static const float FLOAT_CONST = 0.001f; // C2864
};

Gives the following error:

1>c:\projects\myproject\Constant_definitions.h(71) : error C2864: 'MY_CONSTS::FLOAT_CONST' : only static const integral data members can be initialized within a class

Am I missing something?

kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
Jon Cage
  • 36,366
  • 38
  • 137
  • 215

7 Answers7

63

To answer the actual question you asked: "because the standard says so".

Only variables of static, constant, integral types (including enumerations) may be initialized inside of a class declaration. If a compiler supports in-line initialization of floats, it is an extension. As others pointed out, the way to deal with static, constant, non-integral variables is to define and initialize them in the class's corresponding source file (not the header).

C++ Standard Section 9.2 "Class Members" item 4:

A member-declarator can contain a constant-initializer only if it declares a static member (9.4) of const integral or const enumeration type, see 9.4.2.

Section 9.4.2 "Static Data Members" item 2:

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.

Tyler McHenry
  • 74,820
  • 18
  • 121
  • 166
  • 10
    Indeed. Good that C++0x will support in-class float initializers tho. – Johannes Schaub - litb Mar 16 '10 at 12:33
  • 4
    Another good way would be to make a function returning that value. The benefit is that the value is visible to the compiler (which can inline the function). An in-.cpp definition won't expose the value to other TUs. – Johannes Schaub - litb Mar 16 '10 at 12:48
  • 1
    There's another benefit to the function approach: you avoid the "static initialization order fiasco" that can happen if you try to initialize static values using other static values from other compilation units (although this will probably not happen with built-in types such as float). – Tyler McHenry Mar 16 '10 at 12:59
  • 2
    And in c++0x you can declare the return type as `constexpr float` [http://en.wikipedia.org/wiki/C%2B%2B0x#Generalized_constant_expressions] – KitsuneYMG Mar 16 '10 at 14:06
40

You should initialize them in the body of one of your cpp files:

class MY_CONSTS
{
public :
    static const long   LONG_CONST = 1;      // Compiles 
    static const float FLOAT_CONST;
};

const float MY_CONSTS::FLOAT_CONST = 0.001f;
Will
  • 73,905
  • 40
  • 169
  • 246
  • 3
    Some compilers will still expect you to have another line in the cpp file to define space for `LONG_CONST`: `const long MY_CONSTS::LONG_CONST;` Note that you don't repeat the initialization here. – Adrian McCarthy Mar 16 '10 at 15:08
  • 3
    @Adrian: the difference is that these compilers are broken. – sbi Mar 16 '10 at 18:33
  • 1
    @sbi: What? Inline initialization of a static member doesn't remove the need for a definition. – Ben Voigt Jan 10 '12 at 23:20
19

See Stroustrup's explanation. Relevant quote:

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. See D&E for an explanation of C++'s design tradeoffs.

JRL
  • 76,767
  • 18
  • 98
  • 146
  • 14
    That doesn't really answer the question. Why can a `long` be initialized and a `float` not? Presumable a `long` doesn't need 'to be stored in memory as [an object]' but a `float` does? How does that make sense? – KitsuneYMG Mar 16 '10 at 14:08
  • 17
    long constant can be made a part of the CPU instruction (at least on Intel CPUs). A float can't help being in memory. Using a slight abuse of notation, you can say that long's can be inlined, and floats cannot. – Seva Alekseyev Mar 16 '10 at 14:52
9

The rationale under the standard wording that others have given is the same for which template arguments can't be floating point number. To get consistent result you'll need the compiler implement the same evaluation as the one done at compile time, and that can be complicated for cross-compiler and in the case where the program plays with rounding mode.

From memory, in C++0X, the notion of constant expression has been extended and so your code would be valid (but it is unspecified in the result of floating point constant expressions are the same when evaluated at run-time or at compile time).

AProgrammer
  • 51,233
  • 8
  • 91
  • 143
6

In C++11 and later, you can use constexpr to define class float constants:

class MY_CONSTS
{
public:
    static constexpr long  LONG_CONST = 1;      
    static constexpr float FLOAT_CONST = 0.001f;
};
Alex
  • 61
  • 1
  • 1
3

what about:

class MY_CONSTS
{
public :
    static const long   LONG_CONST;
    static const float FLOAT_CONST;
};

const long MY_CONSTS::LONG_CONST = 1;
const float MY_CONSTS::FLOAT_CONST = 0.001f;

(though, i cannot give any explanation of this specific case...)

Adrien Plisson
  • 22,486
  • 6
  • 42
  • 73
3

From standard 9.4.2/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.

And 5.19/1:

In several places, C + + requires expressions that evaluate to an integral or enumeration constant: as array bounds (8.3.4, 5.3.4), as case expressions (6.4.2), as bit-field lengths (9.6), as enumerator initializers (7.2), as static member initializers (9.4.2), and as integral or enumeration non-type template arguments (14.3). constant-expression: conditional-expression An integral constant-expression can involve only literals (2.13), enumerators, const variables or static data members of integral or enumeration types initialized with constant expressions (8.5), non-type template parameters of integral or enumeration types, and sizeof expressions. Floating literals (2.13.3) can appear only if they are cast to integral or enumeration types. Only type conversions to integral or enumeration types can be used. In particular, except in sizeof expressions, functions, class objects, pointers, or references shall not

Andreas Brinck
  • 51,293
  • 14
  • 84
  • 114