13

Here are two line of code:

static const double RYDBERG_CONST_EV = 13.6056953;
static const char CHAR_H_EDGE = '-';

The second line compiles without errors, the first line does not compile. (Error: 'constexpr' needed for in-class initialization of static data member...)

The solution is apparently to add the keyword constexpr before the type. This is required because double is not an "integral type". But why does the behaviour differ between integer and floating point types?

FreelanceConsultant
  • 13,167
  • 27
  • 115
  • 225
  • @juanchopanza Alright, that then – FreelanceConsultant Feb 01 '16 at 22:23
  • 5
    @user3728501, from the error message looks like your lines are in class scope, but you didn't mention that in your question. You should make your code "Short, Self Contained, Correct Example", see http://sscce.org . It's a very bad manner to waste time of other people who try to help you by letting them guess what your code is. – Kan Li Feb 01 '16 at 23:08
  • @icando it's mentioned in the title, but really it should be made explicit. – g24l Feb 01 '16 at 23:26
  • There is no need to repeat the 'C++' in the title of your question, especially if it doesn't fit the grammar of the sentence. It adds no information, identifying the language you are talking about is what *tags* are good for. – 5gon12eder Feb 01 '16 at 23:51

3 Answers3

14

I don't believe that there is a good reason for this except that it has grown historically.

The exception for integral types was desirable in pre-C++11 because people wanted to use them as array sizes. This goes along with the other exception for integral constants being treated as constant expressions. An exception that doesn't exist for floating-point types.

const int    ni = 10;
const float  nf = 10.0f;
int numbers1[(unsigned) ni];  // perfectly fine in all versions of C++
int numbers2[(unsigned) nf];  // error in all versions of C++

When C++11 introduced constexpr, it could do anything the special-casing for const integral types could do and much more. And it works the same way for any literal type. So, given a superior tool, there was no need to dilate the existing rules for integral types to floating-point.

Today, the special-casing of integral types is mostly a left-over from the earlier darker days. It cannot be removed from the language because doing so would break existing code that relies on this special-casing but there would be little gains from complicating the language even further by adding more exceptions that would be entirely unneeded today thanks to constexpr. People should be expected to migrate to constexpr and not worry about the old cruft any more. I believe that this was a very reasonable decision but you could certainly argue that another decision should have been made.

Addendum

As T.C. has commented, there has been a (non)-defect report about this issue where the committee confirmed that the behavior won't be changed and people are supposed to start using constexpr.

1826. const floating-point in constant expressions

Section: 5.20 [expr.const] Status: NAD Submitter: Ville Voutilainen Date: 2014-01-04

A const integer initialized with a constant can be used in constant expressions, but a const floating point variable initialized with a constant cannot. This was intentional, to be compatible with C++03 while encouraging the consistent use of constexpr. Some people have found this distinction to be surprising, however.

It was also observed that allowing const floating point variables as constant expressions would be an ABI-breaking change, since it would affect lambda capture.

One possibility might be to deprecate the use of const integral variables in constant expressions.

Additional note, April, 2015:

EWG requested CWG to allow use of const floating-point variables in constant expressions.

Rationale (May, 2015):

CWG felt that the current rules should not be changed and that programmers desiring floating point values to participate in constant expressions should use constexpr instead of const.

5gon12eder
  • 24,280
  • 5
  • 45
  • 92
2

For the wording, § [class.static.data]/3 says:

  1. If a non-volatile const static data member is of integral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression (5.20). A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression.

(emph mine). Note how in case of integral types the static data member can, not must, have an initialization (you can always put it outside the class definition). Plus, the only other way to have an initaliation inside a class definition is by means of constexpr.

The reasoning (IMHO) about allowing integral types (also in C++98) to be initialized in the class definition is in order to enable very simple patterns like this:

class Foo {
    static const size_t arrayLen = 42;
    int foo[arrayLen];
};

which without an in-body initialization would become impossible to implement.

peppe
  • 21,934
  • 4
  • 55
  • 70
  • Not what he asked. He asked why `constexpr` is required for `double` types instead of merely extending the existing rule. – user207421 Feb 01 '16 at 23:36
0

It is known that:

  • static const integral type members can be initialized in class definition.

  • static constexpr members can be initialized in class definition

double is not an integral type and should be marked as a constexpr.

Executables produced in a machine can run in other machines where floating point representation computation is different. Integral Constant Expressions do not change.

Marking an object static says that it can be known by all observers, and making it const is saying that the value does not change. The compiler can generate a value (e.g. 314 ) and put it in the read-only section, because the range is defined in the standard.

On the other hand double is not in the standard and cannot have its ranged check and the value stored at compile-time, in class definitions. One could easily end up with having different object files with objects having a different value for that static const double, thus breaking ODR.

Here is a simple example:

struct foo
{
static char const a = 1/11; // everyone gets 0 at compile-time
};

You would say then , but this can happen for doubles and at first look, something like this

struct foo
{
static double const y=1.0/11.0; // the true value is 0.090909... 
};

seems verifiable, but the representation in double in one machine will be 0.09091 in another 0.090909091 .

Using constexpr permits to say to the compiler that the input necessary to verify this is available at compile-time . However, actual evaluation can happen at run-time.

Since object files produced by C++ compilers can move to machines with different floating point representations , you have to tell that this checking must be made during compile time to ensure that consistency.

The question is a typical example of an XY-problem. Instead of asking , "why do I have to mark anything with constexpr?" a puzzle char-vs-float is given. Now the question is, "why do we have to use constexpr for non-integral types?", and here you can find your answer .

Community
  • 1
  • 1
g24l
  • 3,055
  • 15
  • 28