c++staticstatic-initializationclass-constants

Definition of class constants in cpp class declaration


Although in principle it seems that only integral values can be declared as class constants in the declaration of a class:

struct Foo{
   static const int N=10;
};

In my code, looking for alternatives that would not force me to define a float constant in a compilation unit, I have seen that it is possible to put (and it works):

struct Foo
{
    constexpr static float const D1 = 33.8F;
...
 };

And the truth is that I don't really understand why, nor why the standard would support this syntax and not a more natural one like this one: static const float D1 = 33.8F;

Is this correct? Can someone expert give an explanation please?

struct Foo
{
    constexpr static float const D1 = 33.8F;
...
 };

And it works


Solution

  • but could you explain what is the underlying reason for not allowing the declaration and definition of a class of basic types except int? Why does constexpr allow it and const does not?

    K.O. explanation: historical reasons.

    uninhibited speculation starts below the line of scissors below

    ✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄

    To elaborate:

    While in practice it's pretty fair to assume that the alignment and placement constraints for integer variables are known at compile time, and pretty unambiguous on any given hardware platform, there's quite some freedom for hardware to have or not have floating point units (FPUs), and for compilers to implement floating point operations in software or by using the FPU if available.

    Now, an FPU is a mighty beast. It supports modes of operation – for example, in control applications you'd often want something like saturating math, in which trying to store a value larger than the maximum value just clips the value to the maximum value. There's also flags that enable or disable the raising of interrupts on doing things like division by zero or multiplication by NaN, very much depending on the actual FPU. C++ says you should support IEEE754 floats if supported, otherwise, well, do what you want?

    In this situation, you need to run code to put your FPU in the state you want to make the constant you just declared to actually mean what you intend it to mean.

    So, back in the day, and this is speculation, the standardization committee was not willing to put up with that complexity and amount of possible pitfalls. So, it didn't allow initialization of static float members in the class definition.

    Later, with C++11, constexpr was introduced, and suddenly, all these ambiguities had to be cleaned up by the compiler vendors anyway. constexpr float f = 2.0f * FLOAT32_MAX; needed to have an (implementation-defined) unambiguous value.

    Since constexpr is kind of better than static const (as it doesn't allow you to do strange things like not initialize a constant), no effort was undertaken to extend the behaviour of static const. Your use case is one for constexpr, so use it!

    C++11 to C++17 still treat float differently than int and consorts;

    template <typename T, T constant_value>
    class default_value_wrapper {
      const T value = constant_value;
    };
    
    class container {
      default_value_wrapper<int, 42> def_int;  // OK
      static default_value_wrapper<int, 1337> def_int_constant; //OK
    
      default_value_wrapper<float, 42.0f> def_float; // doesn't work; float is not allowed for non-type arguments
      static default_value_wrapper<float, 13.37f> def_float_constant; // doesn't work, same reason
    };
    

    is illegal.

    That only changed with C++20, when floating point numbers also became structural types. (Not allowing float in a template parameter makes a lot of sense to me; each different template parameters define a new type – that just screams for bugs due to people making wrong assumptions about floating point equality, and thus using the "wrong" overload).

    By the way, just because the C++20 standard says you need to support float as structural type doesn't mean compilers do. Currently, clang seems to be losing the compatibility race, and neither the current release (16.0) nor the current development head (trunk) do any better than "sorry, non-type template argument of type 'float' is not yet supported".