Why are classes not considered completely defined within static data member initializer? - c++

In the c++ standard it is specified that within the class member-specification (class body), the class can be considered completely-defined, but not for static data member initializer [class.mem]:
A class is considered a completely-defined object type (6.9) (or complete type) at the closing } of the
class-specifier. Within the class member-specification, the class is regarded as complete within function
bodies, default arguments, noexcept-specifiers, and default member initializers (including such things in
nested classes). Otherwise it is regarded as incomplete within its own class member-specification.
EDIT: This is a citation from N4687, wording has changed but I do not believe the meaning changed.
I was expecting such code to compile:
struct enum_like
{
static constexpr enum_like enum_member{};
};
Why such a definition disallowed by the C++ standard?
I believe compilers could proceed this way:
read member declaration, not definition until class definition closing brace. (Now the compiler has a completely defined class)
Analyse static data-member initializer (This way compilers have the constant definition of constexpr members)
Analyse other member definitions.
And then resolve recursions for static member intializer as is specified in [decl.init] for non static members!

This rule forbids problematic things like:
struct A {
static constexpr std::size_t N = sizeof(A);
char buffer[N+2];
};

Related

Initialize static float constexpr member in the definition (.cpp file) is it possible

I would like to initialize my private static member "pi" in the definition of the class in order to tidy my code better having the initializations in the .cpp files.
When I try this error shows up: "Declaration of constexpr static data member 'pi' requires an initializer"
I'm using CLion 2018.3.4 and C++ 11.
I tried to work around and the only solution is to initialize the member in the declaration.
Other answers on Stack Overflow provided me a better knowledge but didn't answer my question.
// .h file
class Shape {
public:
virtual void getArea();
private:
static constexpr float pi; // the error shows up here
};
// .cpp file
#include "Shape.h"
const float Shape::pi = 3.14; //here I don't exactly know why it does not require constexpr, but it's fine even with and without const
// what I think this is equivalent to (in .h file)
static constexpr float pi = 3.14;
I was expecting this to work like I was assigning "3.14" in the declaration.
I'm not defining a constructor, in that case I know it wouldn't work because the c'tor is meant to initialize an instance of the class while a static member is supposed to be already initialized being a global element in the "Shape" namespace.
What I suppose it is happening is that the linker tries to initialize the member in the header because it has to be done in the pre-processing phase while the .cpp file is used later.
With static constexpr members you cannot leave off the initializer in the class definition. A constexpr variable must be initialized when declared because it can be used after it is declared in a constant expression. This is detailed in [class.static.data]/3 of the C++11 standard
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 ([expr.const]). 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. [ Note: In both these cases, the member may appear in constant expressions. — end note ] The member shall still be defined in a namespace scope if it is odr-used ([basic.def.odr]) in the program and the namespace scope definition shall not contain an initializer.
emphasis mine
So, with that, your code needs to be
// .h file
class Shape {
public:
virtual void getArea();
private:
static constexpr float pi = 3.14; // we initialize here so it can be used.
};
// .cpp file
constexpr float Shape::pi; // we define here so it can be odr-used
Do note that this has changed in C++17. With the introduction of inline variables static constexpr member variables no longer need to be defined outside of the class. The compiler will handle it for you and ensure only a single defentition of the object exists. You can still define the member if you want, but that ability is deprecated and will most likely be removed in a future standard revision. The new text for [class.static.data]/3 is
If a non-volatile non-inline 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 ([expr.const]). The member shall still be defined in a namespace scope if it is odr-used ([basic.def.odr]) in the program and the namespace scope definition shall not contain an initializer. An inline static data member may be defined in the class definition and may specify a brace-or-equal-initializer. If the member is declared with the constexpr specifier, it may be redeclared in namespace scope with no initializer (this usage is deprecated; see [depr.static.constexpr]). Declarations of other static data members shall not specify a brace-or-equal-initializer.
emphasis mine
and [dcl.constexpr]/1 says that a static constexpr variable is implicitly inline
The constexpr specifier shall be applied only to the definition of a variable or variable template or the declaration of a function or function template. The consteval specifier shall be applied only to the declaration of a function or function template. A function or static data member declared with the constexpr or consteval specifier is implicitly an inline function or variable ([dcl.inline]). If any declaration of a function or function template has a constexpr or consteval specifier, then all its declarations shall contain the same specifier.
emphasis mine

why initialization of static member variable is not allowed inside class but initialization of const static members is allowed?

When I tried to initialize the static member variables at the time of declaration inside the class, compiler is throwing error as expected because we need to explicitly allocate the space for static member variables outside the class. I thought this should be same for static const variables. But to my surprise, initialization of static const member variables inside the class is working fine. Can any one please let me know why normal static member variable initialization is not allowed in the same way?
I assume that you meant
// inside class definition block
static const int a = 0;
static int b = 0; // error
C++ Standard 9.4.2/4,
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 name- space scope if it is used in the program and the namespace scope definition shall not contain an initializer.
It is specified in standard.
Edit:
as M.M pointed out, above quotation is actually not what the Standard says, and the correct one is C++ Standard 12.2.3.2/3
If a non-volatile non-inline 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 (8.20). The member shall still be defined in a namespace scope
if it is odr-used (6.2) in the program and the namespace scope definition shall not contain an initializer. An
inline static data member may be defined in the class definition and may specify a brace-or-equal-initializer .
If the member is declared with the constexpr specifier, it may be redeclared in namespace scope with no
initializer (this usage is deprecated; see D.1). Declarations of other static data members shall not specify a
brace-or-equal-initializer.
One needs a bit of space in memory. Const do not - they can be hard coded.
classes are usually declared in header files. Header files can be included multiple times in body files. If a static member, which needs memory, is defined within a class, than there will be a different copy of such a member in different body files. This will kill the idea of static members.
Constants on the other hand do not use memory and are compile-only constructs. therefore declaring them in the classes does not do any harm.

Inner member visibility in class

According to the C++ standard,
9.2 [class.mem]:
A class is considered a completely-defined object type (3.9) (or
complete type) at the closing } of the class-specifier. Within the
class member-specification, the class is regarded as complete within
function bodies, default arguments, using-declarations introducing
inheriting constructors (12.9), exception-specifications, and
brace-or-equal-initializers for non-static data members (including
such things in nested classes). Otherwise it is regarded as incomplete
within its own class member-specification
So, the code below should compile, and indeed it does
struct Foo{
Foo()
{
Bar bar; // Bar is fully visible here, even though it's defined later
}
//void f(Bar){} // But NOT VISIBLE if used as a function parameter
struct Bar{};
};
int main()
{
Foo foo;
}
Live on Coliru
However, if I uncomment the line that defines the member function void Foo::f(Bar), then the code fails to compile with the error
error: 'Bar' has not been declared
Reading again the standard it indeed seems that function parameters are not considered as places where the class is regarded as complete. However, it does not make any sense at all. Can you shed some light why I cannot use Bar in a function parameter (but otherwise can fully use it inside a function without any issues whatsoever) before its full definition?
In all the cases listed in 9.2 [class.mem] knowing the type can be deferred until the class is fully defined. We can see this rationale listed in defect report 643: Use of decltype in a class member-specification which says:
In the other cases where a class type is considered complete within the definition of the class, it is possible to defer handling the construct until the end of the definition. That is not possible for types, as the type may be needed immediately in subsequent declarations.
As T.C. points out there is also issues of lookup involved as defect report 325: When are default arguments parsed? and defect report 1352 deal with. The later one also mentions the same technique of being able to defer parsing till the class is complete:
The rules regarding class scope and when the class is considered to be complete (normally implemented by deferred parsing of portions of class member declarations) are inconsistent and need to be clarified.
From the 03 standard, 3.4.1/8 (Unqualified name lookup):
A name used in the definition of a member function (9.3) of class X following the function’s declarator-id29)
shall be declared in one of the following ways:
— before its use in the block in which it is used or in an enclosing block (6.3), or
— shall be a member of class X or be a member of a base class of X (10.2), or
— if X is a nested class of class Y (9.7), shall be a member of Y, or shall be a member of a base class of Y
(this lookup applies in turn to Y’s enclosing classes, starting with the innermost enclosing class),30) or
— if X is a local class (9.8) or is a nested class of a local class, before the definition of class X in a block
enclosing the definition of class X, or
— if X is a member of namespace N, or is a nested class of a class that is a member of N, or is a local class
or a nested class within a local class of a function that is a member of N, before the member function
definition, in namespace N or in one of N’s enclosing namespaces.

In-class initialization of static data members

In C++, static members may not be initialized in the class body with these exceptions:
static members of const integral type can be
static members of constexpr literal type must be
Can you explain why these exceptions?
Also, this holds:
Even if a const static data member is initialized in the class body, that member ordinarily should be defined outside the class definition.
This I never understood at all. What's the point of this extra definition?
Just trying to get some intuitions here.
Why can there be an initializer in the class definition?
Concerning the two exceptions for const and constexpr static data members:
[class.static.data]/3
[ Note: In both these cases, the member may appear in constant expressions. — end note ]
I.e. with an initializer, you may use them in constant expressions, e.g.
struct s
{
static std::size_t const len = 10;
int arr[len];
};
std::size_t const s::len;
If len wasn't initialized in the class definition, the compiler couldn't easily know its value in the next line to define the length of arr.
One could argue about allowing initializers for of non-const, non-constexpr static data members in the class definition, but this could interfere with the initialization order:
[basic.start.init]/2
Definitions of explicitly specialized class template static data members have ordered initialization. Other class template static data members (i.e., implicitly or explicitly instantiated specializations) have unordered initialization. Other non-local variables with static storage duration have ordered initialization.
That is, the order of the definitions including initializers is important. The order of (dynamic) initialization of non-local objects is only defined within a translation unit, this is another reason why there has to be a definition including initializer for non-const, non-constexpr static data members.
What's the point of this extra definition?
This has already been answered in the comments IMO. You might want to add the ODR, that is, as a name with external linkage, the static data member must (only) be defined in one translation unit (if it's ODR-used). It's up to the programmer to choose this translation unit.

Scope of evaluation of array bound of static data member

I was going to file a bug against GCC, but then realized that if my interpretation of the Standard is correct, it's a core language defect, not a compiler bug.
When a static data member of array type is defined outside class scope, identifiers in the array bound are looked up in class scope.
§9.4.2 [class.static.data] says "The initializer expression in the definition of a static data member is in the scope of its class (3.3.7)," but doesn't say anything about the declarator itself. It seems that this is the only name lookup context within a declarator.
§8.4.2 [dcl.array] doesn't mention the scope of the array bound, so by default the expression is evaluated in the enclosing scope, which is the namespace.
class X {
static int const size = some_complicated_metafunction<>::value;
static int arr[ size ];
};
// "size" should be qualified as "X::size", which is an access violation.
int X::arr[ size ] = {};
The problem is that if the array bound is not evaluated in class scope, there is no way to access private class members, and some_complicated_metafunction<> would have to be respecified. The array bound needs the same scope as the initializer, for essentially the same reason as the initializer. (Although not quite as strong, since constant expressions can always be recomputed, unlike the address of a private object.)
Am I missing something or is a DR in order?
I think the array bound in a member definition is in class scope. Standard 3.3.7/1:
The following rules describe the scope of names declared in classes.
...
5) The potential scope of a declaration that extends to or past the end of a class definition also extends to the regions defined by its member definitions, even if the members are defined lexically outside the class (this includes static data member definitions, nested class definitions, member function definitions (including the member function body and any portion of the declarator part of such definitions which follows the declarator-id, including a parameter-declaration-clause and any default arguments (8.3.6). ...
Here the declarator-id is X::arr.