tags:

views:

2510

answers:

4

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
+1  A: 

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;
Mark Ransom
Yes, I steered away from this possible solution only because I thought mine was a tad easier to read. I don't like *having* to declare and initialize a value in separate files (My class is in .h). Thank you.
Jeffrey Martinez
+10  A: 

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

Adam Rosenfield
static const double d = 1.0; isn't valid C++. it shouldn't compile at all. that form is only allowed for integral types. that's the reason that min and max in numeric_limits<T> are functions and not static constants. i'm not sure why it compiles for you.
Johannes Schaub - litb
it seems to be a gcc extension. compiling with -pedantic yields: "foo.cpp:4: error: ISO C++ forbids initialization of member constant 'd' of non-integral type 'const double'"
Johannes Schaub - litb
+6  A: 

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.

Johannes Schaub - litb
I didn't quiet understand "Allowing floating point template parameters would bring its own set of rules not really worth the trouble.". Also having difficulty in getting "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."
Chubsdad
+1  A: 

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.

Michael Burr
c++1x will fix that misfeature: (recent draft): "An object or non-overloaded function whose name appears as a potentially evaluated expression is used unless it is an object that satisfies the requirements for appearing in a constant expression". statics must only be defined if used,so this solves it
Johannes Schaub - litb
providing no definitions in most todays' implementations work because of this: "If a program contains a violation of a rule for which no diagnostic is required, this International Standard places no requirement on implementations with respect to that program."
Johannes Schaub - litb
the rule stating you need to provide a definition is marked with "no diagnostic required". So most compiles (including comeau) just go OK unless you take the address of the object and the object is not initialized within the class definition, at which time a linker error will result.
Johannes Schaub - litb
so summary. i completely agree with your post. just wanted to post some additional information. i hope you don't mind :)
Johannes Schaub - litb
@litb: don't mind at all - I know that compilers often do not complain if no definition is given for a `static const int` if the address is not required, and it probably should have been mentioned, but, I didn't want to edit the Stroustrup quote.
Michael Burr
ok. today i found out that c++03 already changed the rules and allows to omit the definition if the variable appears where a integral constant expression is required. good to know :)
Johannes Schaub - litb
@litb: are you sure? - C++03 9.4.2 Para 4 seems to still clearly state that exactly one definition must exist (if the member is used).
Michael Burr