Why can't I have a non-integral static const member in a class?

C++

C++ Problem Overview


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

C++ Solutions


Solution 1 - C++

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

Solution 2 - C++

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.

Solution 3 - C++

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.

Solution 4 - C++

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;

Solution 5 - C++

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJeffrey MartinezView Question on Stackoverflow
Solution 1 - C++Adam RosenfieldView Answer on Stackoverflow
Solution 2 - C++Johannes Schaub - litbView Answer on Stackoverflow
Solution 3 - C++Michael BurrView Answer on Stackoverflow
Solution 4 - C++Mark RansomView Answer on Stackoverflow
Solution 5 - C++Liu NickView Answer on Stackoverflow