24

Static integral data members initialized in the class definition may be declared const or constexpr, but non-integral static data members initialized in the class definition must be constexpr:

class MyClass {
  static const     int   w = 5;          // okay
  static constexpr int   x = 5;          // okay
  static const     float y = 1.5;        // error!
  static constexpr float z = 1.5;        // okay
};

Does anybody know why the declaration for y is not permitted? The part of the Standard making it illegal is 9.4.2/3, but why is it illegal?

Evan Teran
  • 87,561
  • 32
  • 179
  • 238
KnowItAllWannabe
  • 12,972
  • 8
  • 50
  • 91

3 Answers3

7

Prior to C++11, you could not initialize static members of non-integral/enumeration types in the class declaration (but you can outside of the class declaration). The rule governing constexpr carries that forward, but allows you to initialize it using constexpr in the class declaration (so you don't need code like the following anymore):

struct A
{
    static const float pi;
};

const float A::pi = 3.1415;

One of the side effects of this rule was to simplify your class structure instead of making it ugly (like the above code).

One of the reasons why this was the case prior to C++11's addition of constexpr was the standard did not specify how floating points were to be implemented (it is left to the processor/architecture - for example, when you say float x = 1.6f, it is actually 1.6000000000024 on most systems).

Zac Howland
  • 15,777
  • 1
  • 26
  • 42
  • You've got the right idea, but a bad example. 1.5f can be represented exactly in either a binary or a decimal floating point type, and should be stored as exactly 1.5 on pretty much any system. –  Jan 26 '14 at 15:45
  • @hvd The entire point was that you cannot rely on that as it is platform dependent. – Zac Howland Jan 26 '14 at 19:43
  • You wrote "it is actually 1.499999999999999 on most systems", which is saying more than merely that it is not required to be stored exactly. I would be very surprised if you find any system on which it is not stored exactly, even though you're right that it's permitted. –  Jan 26 '14 at 21:33
  • @hvd If it makes you feel better, replace it with 1.6f and 1.600000024. The point remains the same, though. – Zac Howland Jan 26 '14 at 21:37
  • That's what I tried to point out in my first comment, your point is valid, it's just an example that doesn't (on typical implementations) illustrate the point. The 1.6f you mention now does. –  Jan 26 '14 at 21:42
  • It doesn't matter that your literal is quantized to the closest floating representation. what matters is the rule the compiler should regard, must it respect some abstract set of rules, or the compilation target CPU rules ? the later is also hard because one CPU generation can support one instruction set and still have many different discrepancies between each version. – v.oddou Dec 09 '14 at 04:01
2

float is a bit of a harder one to describe the motivation for, but imagine a class member:

class MySpecialInt {
public:
    constexpr MySpecialInt(const int & other) {
    }
};
class MyClass {
    static const     MySpecialInt a = 5; // error
    static constexpr MySpecialInt b = 5; // okay
};

a in this scenario could have some non-trivial construction that potentially violates (or at least grossly complicates) the one-definition-rule. Because constexpr has guaranteed restrictive compile-time properties, b's copy constructor must also be constexpr and is therefore guaranteed to return a well defined value at compile-time (and NOT violate the one-definition-rule)

Why float exhibits this behavior I believe is just for legacy reasons since float has never been traditionally initialize-able like this ("because the standard says so"), so they caught initializing static const float members under the umbrella of constexpr.

Sam Cristall
  • 4,328
  • 17
  • 29
  • I'd be interested to hear more details about how the ODR could feed into this. Each of `a` and `b` must be initialized exactly once. That means that the appropriate constructor must be called exactly once. In the case of `a`, we don't know if construction will occur during compilation or at runtime, but under what conditions could `a`, `b`, or the `MySpecialInt` constructor be defined more than once (such that it's due to the use of `constexpr` versus `const`)? – KnowItAllWannabe Oct 25 '13 at 05:50
  • They are actually defined once per translation unit. So if you include this class in two spots, `a` and `b` are defined twice, but because `b` is `constexpr` it is guaranteed to be consistently the same value, so it is legal. `a` could have some internal counter or other logic that would cause `a` to be different between translation units, which is why it is illegal to declare it as merely `static` `const`. – Sam Cristall Oct 25 '13 at 14:58
  • Per 9.4.2/3, declarations of static data members with initializers are not definitions, and definitions of such members are required outside the class if they are odr-used. But the problem you refer to, if it exists, is just as applicable to `const`--not `constexpr`--members of integral type that are dynamically initialized. The function called to produce the initialization value could return different values on different calls. So I don't see how this explains the restriction for non-integral static data members. – KnowItAllWannabe Oct 25 '13 at 17:05
  • In that same section it states that integral types' initializer-clauses must all be constant expressions, so they intrinsically cannot be dynamically initialized. – Sam Cristall Oct 25 '13 at 17:32
  • Okay, but the same restriction applies to const static data members, so if the declaration for `a` were legal, `MySpecialInt`'s constructor would still have to be `constexpr`. If that's the case, it's still not clear (at least to me) why `constexpr` is okay, but `const` is not. I must still be missing something. – KnowItAllWannabe Oct 25 '13 at 19:44
  • `MySpecialInt` is *not* an integral or enumeration type, it is a "literal type" which falls into the second category of `9.4.2/3`. Only integral/enum types can be declared `static const` and initialized inline, but even then they must be initialized with a constant expression. Literal types must be declared `static constexpr` *and* they must be initialized with a constant expression. – Sam Cristall Oct 25 '13 at 19:51
  • Right, but this brings us back to square one: *why* the different treatment of integral/enumeration and other literal types? For integral types, both const and constexpr members require constant expression initializers, so why can't non-integral literal types also be declared const (still requiring a constant expression initializer)? – KnowItAllWannabe Oct 25 '13 at 20:27
  • Because it is a new feature, and typically the new standards isolate new features with new syntax, so as to make it clear which standard is being used. If the `static const` worked on literal types, some snippet might look like it was C++03 compliant when it cannot be. Arguably, for consistency, modern C++ should deprecate `static const` and everything should just be `static constexpr`. – Sam Cristall Oct 25 '13 at 20:53
1

It may be because of the fact that non integral i may also includes data type like char and that's why you can't make them constant and requires constant expression.But in case of integral, you can either make them constant expression or constant. So, because of fact that char can only be a constant expression, so it is illegal for all non integral values.