Constant Expression Initializer for Static Class Member of Type Double

Constant expression initializer for static class member of type double

In C++03 we were only allowed to provide an in class initializer for static member variables of const integral of enumeration types, in C++11 we could initialize a static member of literal type in class using constexpr. This restriction was kept in C++11 for const variables mainly for compatibility with C++03. We can see this from closed issue 1826: const floating-point in constant expressions which says:

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.

CWG ended up closing this request as not a defect(NAD), basically saying:

that programmers desiring floating point values to participate in constant expressions should use constexpr instead of const.

For reference N1804 the closest draft standard to C++03 publicly available in section 9.4.2 [class.static.data] says:

If a static data member is of const integral or const enumeration type, its declaration in the class definition can
specify a constant-initializer which shall be an integral constant expression (5.19). In that case, the member can appear
in integral constant expressions. The member shall still be defined in a namespace scope if it is used in the program and
the namespace scope definition shall not contain an initializer.

and the draft C++11 standard section 9.4.2 [class.static.data] says:

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.19). 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. [...]

this is pretty much the same in the draft C++14 standard.

C++ and C++11 class static member, double should use constexpr while int can be const , why?

const follows the original language specification defined in C++98 and C++03. It was generally disallowed to supply an in-class initalizers for static const members in C++98. The possibility to do so for static const objects of integral and enum types in C++98 was part of special treatment given to these types.

constexpris a new feature introduced in C++11. It is designed differently and works uniformly for all types.

So, you can just use constexpr for both integer and floating point types and forget about any non-uniformities.

If you continue to use const in such contexts, you will have to deal with C++98 legacy. However, C++17 will introduce inline variables, which should also make it possible to use in-class initializers for inline static const objects of any type.

C++ (static const double variable) cannot appear in constant-expression

Short answer: use constexpr instead of const.

Long answer: there are special provisions in old C++03 which allow class members which are static integral constants be used in constant expressions. This provision does not apply to non-integral (doubles).

With C++11, constexpr removed this limitation.

Initializing const member within class declaration in C++

In C++11, non-static data members, static constexpr data members, and static const data members of integral or enumeration type may be initialized in the class declaration. e.g.

struct X {
int i=5;
const float f=3.12f;
static const int j=42;
static constexpr float g=9.5f;
};

In this case, the i member of all instances of class X is initialized to 5 by the compiler-generated constructor, and the f member is initialized to 3.12. The static const data member j is initialized to 42, and the static constexpr data member g is initialized to 9.5.

Since float and double are not of integral or enumeration type, such members must either be constexpr, or non-static in order for the initializer in the class definition to be permitted.

Prior to C++11, only static const data members of integral or enumeration type could have initializers in the class definition.

static const double initialization in C++

static data members which are not constexpr can only be initialised directly at their declaration in the class definition if they are of integral or enumeration type. All other data types must be given a separate definition in a source file, and can only be initialised at that definition. So change your class definition to this:

class myclass
{
private:
static const double foo;
static const double bar;
};

and introduce these definitions into exactly one .cpp file:

const double myclass::foo = 3.1415;
const double myclass::bar = 12345.0 * foo;

If you have access to sufficiently modern C++, you have an alternative option of changing the in-class declarations to constexpr:

class myclass
{
private:
static constexpr double foo = 3.1415;
static constexpr double bar = 12345.0 * foo;
};

That way, they will not require a definition in a source file unless you use them as objects instead of as values (e.g. if you take their address). However, GCC 4.3.3 does not support that part of C++11.

Wrong static const initialization that compiles and works

The Foo1 case is indeed non-conforming and if we build using -std=c++98 -pedantic gcc will warn as follows (see it live):

error: floating-point literal cannot appear in a constant-expression
static const double A=2.5;
^
warning: ISO C++ forbids initialization of member constant 'Foo1::A' of non-integral type 'const double' [-Wpedantic]

While compiling without -pedantic does not yield any error or warning (see it live)

So this must be an extension and if we use clang using -std=C++98 -pedantic we see this message:

warning: in-class initializer for static data member of type 'const double' is a GNU extension [-Wgnu-static-float-init]
static const double A=2.5;
^ ~~~

which seems to confirm this is an extension.

This restriction on floating point was kept in C++11 to remain compatible with C++03 and to encourage consistent use of constexpr see: Constant expression initializer for static class member of type double.

This is also the case for Foo2 initializing C is allowed as an extension, the result of the division will be int since the type of the result depends on the type of the operands and does not depend on what you assign it to.

Update

This is a depreciated extension:

G++ allows static data members of const floating-point type to be declared with an initializer in a class definition. The standard only allows initializers for static members of const integral types and const enumeration types so this extension has been deprecated and will be removed from a future version.

There is a more detailed gcc bug report that discusses the validity of the extension and other related issues around it.

It seemed odd that using -pedantic was sufficient by itself to turn this into an error, there is a gcc bug report that covers that.

Static member access in constant expressions

Clang seems to be in the right. When accessing a static member with the member access syntax [class.static/1]:

A static member s of class X may be referred to using the qualified-id
expression X​::​s; it is not necessary to use the class member access
syntax to refer to a static member. A static member may be referred to
using the class member access syntax, in which case the object
expression is evaluated.

So s.v() will cause s to be evaluated. Now, according to [expr.const/2.11], s is not a constant expression:

2 An expression e is a core constant expression unless the evaluation
of e, following the rules of the abstract machine, would evaluate one
of the following expressions:

[...]

an id-expression that refers to a variable or data member of reference
type unless the reference has a preceding initialization and either:

(2.11.1) - it is initialized with a constant expression or

(2.11.2) - its lifetime began within the evaluation of e;

s doesn't have a preceding initialization with a constant expression, not in the scope of foo.


If you want to access the static members based of a function parameter, without hard-coding the type, the way forward is std::remove_reference_t<decltype(s)>. This is accepted by Clang and GCC both:

#include <type_traits>

struct S
{
constexpr static auto s_v = 42;
constexpr static auto v() { return s_v; }
};

constexpr auto foo(S const& s)
{
constexpr auto v = std::remove_reference_t<decltype(s)>::v();
return v;
}

constexpr auto bar(S const& s)
{
constexpr auto v = std::remove_reference_t<decltype(s)>::s_v;
return v;
}

int main() {}

static const double cannot have an in-class initializer. why is it so?

The logic implemented by the C++03 language standard is based on the following rationale.

In C++ an initializer is a part of object definition. What you write inside the class for static members is actually only a declaration. So, formally speaking, specifying initializers for any static members directly inside the class is "incorrect". It is contrary to the general declaration/definition concepts of the language. Whatever static data you declare inside the class has to be defined later anyway. That's where you will have your chance to specify the initializers.

An exception from this rule was made for static integer constants, because such constants in C++ can form Integral Constant Expressions (ICEs). ICEs play an important role in the language, and in order for them to work as intended the values of integral constants have to be visible in all translation units. In order to make the value of some constant visible in all translation units, it has to be visible at the point of declaration. To achieve that the language allows specifying the initializer directly in class.

Additionally, on many hardware platforms constant integer operands can be embedded directly into the machine commands. Or the constant can be completely eliminated or replaced (like, for example, multiplication by 8 can be implemented as a shift by 3). In order to facilitate generation of machine code with embedded operands and/or various arithmetical optimizations it is important to have the values of integral constants visible in all translation units.

Non-integral types do not have any functionality similar to ICE. Also, hardware platforms do not normally allow embedding non-integral operands directly into the machine commands. For this reason the above "exception from the rules" does not extend to non-integral types. It would simply achieve nothing.



Related Topics



Leave a reply



Submit