75

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
2
  • what compiler/platform, or are you seeing it on multiples?
    – warren
    Commented Dec 16, 2008 at 2:04
  • What error message are you getting in VS7.1? Commented Dec 16, 2008 at 2:10

5 Answers 5

49

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

6
  • 8
    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. Commented Dec 16, 2008 at 2:27
  • 3
    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'" Commented Dec 16, 2008 at 2:29
  • @JohannesSchaub-litb : thank you for the investigation. this answer should be edited at the top to signal that.
    – v.oddou
    Commented Dec 9, 2014 at 3:43
  • What is the meaning of "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." ? Can you shed more light on this ? Commented Aug 11, 2015 at 20:43
  • the compiler usually doesn't have to ever create a memory address for the constant: As a thought experiment, would taking the address of a constant and storing in a variable force the compiler to create storage for the constant?
    – kevinarpe
    Commented Aug 23, 2016 at 13:34
18

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.

5
  • 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
    Commented Sep 27, 2010 at 2:17
  • 2
    @Chubsdad the former is because of the usual inaccuracy of floating point calculations, which can be different from implementation to implementation. While 1+1 is always 2 on any implementation, similarly simple calculations can yield different results with floating point math on different floating point models (mind me, I'm not fond on these issues but I do know they exist). Commented Jan 30, 2011 at 0:22
  • For the latter issue, I don't really know the rationale. Please note that a compiling environment can be different from the execution environment for cross-compilers. The rationale here maybe to make sure that a great accuracy won't be made worse by calculating the result at compile time. But maybe you can make a separate SO question out of this. Commented Jan 30, 2011 at 0:24
  • 1
    Not only that, but one compiled code (eg armv5) can run on multiple CPUs. eg. an ArmV7 cpu. Which could implement different (intermediary) floating precisions. PowerPC is an example of a CPU that behaves differently from intel. Some old intel (P6 era) had some bugs in it... Even for only one binary the execution can be different. The standard comitee feard this can of worms, and decided to rule out static floats which makes sense to me.
    – v.oddou
    Commented Dec 9, 2014 at 3:49
  • Note DR 1826 explains why they kept this restriction for C++11. Commented Apr 10, 2017 at 16:51
7

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.

7
  • 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 Commented Dec 16, 2008 at 4:26
  • 1
    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." Commented Dec 16, 2008 at 4:32
  • 1
    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. Commented Dec 16, 2008 at 4:35
  • 2
    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 :) Commented Feb 23, 2009 at 19:09
  • 2
    @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). Commented Nov 12, 2009 at 20:19
4

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;
1
  • 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. Commented Dec 16, 2008 at 2:04
0

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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.