74
votes

I noticed C++ will not compile the following:

class No_Good {
  static double const d = 1.0;
};

However it will happily allow a variation where the double is changed to an int, unsigned, or any integral type:

class Happy_Times {
  static unsigned const u = 1;
};

My solution was to alter it to read:

class Now_Good {
  static double d() { return 1.0; }
};

and figure that the compiler will be smart enough to inline where necessary... but it left me curious.

Why would the C++ designer(s) allow me to static const an int or unsigned, but not a double?

Edit: I am using visual studio 7.1 (.net 2003) on Windows XP.

Edit2:

Question has been answered, but for completion, the error I was seeing:

error C2864: 'd' : only const static integral data members can be initialized inside a class or struct
5
what compiler/platform, or are you seeing it on multiples?warren
What error message are you getting in VS7.1?Jesse Beder

5 Answers

49
votes

The problem is that with an integer, the compiler usually doesn't have to ever create a memory address for the constant. It doesn't exist at runtime, and every use of it gets inlined into the surrounding code. It can still decide to give it a memory location - if its address is ever taken (or if it's passed by const reference to a function), that it must. In order to give it an address, it needs to be defined in some translation unit. And in that case, you need to separate the declaration from the definition, since otherwise it would get defined in multiple translation units.

Using g++ with no optimization (-O0), it automatically inlines constant integer variables but not constant double values. At higher optimization levels (e.g. -O1), it inlines constant doubles. Thus, the following code compiles at -O1 but NOT at -O0:

// File a.h
class X
{
 public:
  static const double d = 1.0;
};

void foo(void);

// File a.cc
#include <stdio.h>

#include "a.h"

int main(void)
{
  foo();
  printf("%g\n", X::d);

  return 0;
}

// File b.cc
#include <stdio.h>

#include "a.h"

void foo(void)
{
  printf("foo: %g\n", X::d);
}

Command line:

g++ a.cc b.cc -O0 -o a   # Linker error: ld: undefined symbols: X::d
g++ a.cc b.cc -O1 -o a   # Succeeds

For maximal portability, you should declare your constants in header files and define them once in some source file. With no optimization, this will not hurt performance, since you're not optimizing anyways, but with optimizations enabled, this can hurt performance, since the compiler can no longer inline those constants into other source files, unless you enable "whole program optimization".

18
votes

I see no technical reason why

struct type {
    static const double value = 3.14;
};

is forbidden. Any occasion you find where it works is due to non-portable implementation defined features. They also seem to be of only limited use. For integral constants initialized in class definitions, you can use them and pass them to templates as non-type arguments, and use them as the size of array dimensions. But you can't do so for floating point constants. Allowing floating point template parameters would bring its own set of rules not really worth the trouble.

Nonetheless, the next C++ version will allow that using constexpr:

struct type {
    static constexpr double value = 3.14;
    static constexpr double value_as_function() { return 3.14; }
};

And will make type::value a constant expression. In the meantime, your best bet is to follow the pattern also used by std::numeric_limits:

struct type {
    static double value() { return 3.14; }
};

It will not return a constant expression (value is not known at compile time), but that only matters theoretical, since practical the value will be inlined anyway. See the constexpr proposal. It contains

4.4

Floating-point constant expressions

Traditionally, evaluation of floating-point constant expression at compile-time is a thorny issue. For uniformity and generality, we suggest to allow constant-expression data of floating point types, initialized with any floating-point constant expressions. That will also increase compatibility with C99 [ISO99, §6.6] which allows

[#5] An expression that evaluates to a constant is required in several contexts. If a floating expression is evaluated in the translation envi- ronment, the arithmetic precision and range shall be at least as great as if the expression were being evaluated in the execution environ- ment.

7
votes

It doesn't really give a rationale, but here's what Stroustrup has to say about this in "The C++ Programming Language Third Edition":

10.4.6.2 Member Constants

It is also possible to initialize a static integral constant member by adding a constant-expression initializer to its member declaration. For example:

class Curious {
    static const int c1 = 7;        // ok, but remember definition
    static int c2 = 11;             // error: not const
    const int c3 = 13;              // error: not static
    static const int c4 = f(17);    // error: in-class initializer not constant
    static const float c5 = 7.0;    // error: in-class not integral
    // ...
};

However, an initialized member must still be (uniquely) defined somewhere, and the initializer may not be repeated:

const int Curious::c1;  // necessary, but don't repeat initializer here

I consider this a misfeature. When you need a symbolic constant within a class declaration, use an enumerator (4.8, 14.4.6, 15.3). For example:

class X {
    enum { c1 = 7, c2 = 11, c3 = 13, c4 = 17 };
    // ...
};

In that way, no member definition is needed elsewhere, and you are not tempted to declare variables, floating-point numbers, etc.

And in Appendix C (Technicalities) in Section C.5 (Constant Expressions), Stroustrup has this to say about "constant expressions":

In places such as array bounds (5.2), case labels (6.3.2), and initializers for enumerators (4.8), C++ requires a constant expression. A constant expression evaluates to an integral or enumeration constant. Such an expression is composed of literals (4.3.1, 4.4.1, 4.5.1), enumerators (4.8), and consts initialized by constant expressions. In a template, an integer template parameter can also be used (C.13.3). Floating literals (4.5.1) can be used only if explicitly converted to an integral type. Functions, class objects, pointers, and references can be used as operands to the sizeof operator (6.2) only.

Intuitively, constant expressions are simple expressions that can be evaluated by the compiler before the program is linked (9.1) and starts to run.

Note that he pretty much leaves out floating point as being able to play in 'constant expressions'. I suspect that floating point was left out of these types of constant expressions simply because they are not 'simple' enough.

4
votes

I don't know why it would treat a double different from an int. I thought I had used that form before. Here's an alternate workaround:

class Now_Better
{
    static double const d;
};

And in your .cpp file:

double const Now_Better::d = 1.0;
0
votes

here is my understanding based on Stroustrup's statement about in-class definition

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.

http://www.stroustrup.com/bs_faq2.html#in-class

so basically, this is not allowed because C++ do not allow this. In order to make linker rules more simple, C++ requires that every object has a unique definition.

static member has only one instance in the class scope, not like regular static variables used heavily in C, which has only one instatnce inside one translation unit.

If static member is defined in class, and the class definition will be included into many translation unit, so that the linker has to do more work to decide which static member should be used as the only one through all the related translation unit.

But for regular static variables, they can only be used inside one translation unit, even in the case different static variables in different translation unit with same name, they will not affect each other. Linker can do simple work to link regular static variables inside one translation unit.

in order to decrease the complications and give the base function, C++ provide the only in-class definition for a static const of integral or enumeration type.