[[maybe_unused]] applied to static data members - c++

The draft standard states about [[maybe_unused]] in 10.6.6 item 2
"The attribute may be applied to the declaration of a class, a typedef-name, a variable, a non-static data member, a function, an enumeration, or an enumerator."
Is there any reason to exclude static data members from this? i.e.
struct Foo {
[[maybe_unused]] static inline int foo = 0;
};
I ask as I have a static data member whose type has a non trivial constructor that does useful stuff but is otherwise unused.

[basic]/6 says that any object declaration constitutes a variable. “non-static data member” appears in the list alongside “variable” because a non-static data member of reference type is not a variable.

Related

How to understand the concept of variable

The concept of the variable is defined as:
A variable is introduced by the declaration of a reference other than a non-static data member or of an object. The variable's name, if any, denotes the reference or object.
The relevant concept of the declaration of an object is defined as:
dcl.dcl#dcl.pre-10
If the decl-specifier-seq contains no typedef specifier, the declaration is called a function declaration if the type associated with the name is a function type ([dcl.fct]) and an object declaration otherwise.
So, consider the below code, which entities are called variable?
class A{
int a; // #1 `a` can be called a variable?
int& rf; // #2
};
void fun(int p, int& prf){ // #3 where p can be called a variable, and how about `prf`
}
int main(){
int data = 0; // data is absolutely called a variable
int& ref = data;// ref is absolutely called a variable
}
I wonder whether #1 can be called a variable? Sometimes, the name a is introduced by member-declaration. Hence, it's a member of class A. But sometimes, it is also called member subobject. According to the definition of terminology variable, it's introduced by a declaration of an object.
So, can name a be called variable? Is it call a member or a member subobject? If it is the latter, I think it should be called a variable. Moreover, its associated type is not a function type.
About #2, it's just a data member of class A and is definitely not a variable(it's a non-static data member of class A of reference type).
Are parameters int p and int& prf in the declaration of fun called variable? According to [dcl.dcl#dcl.pre-10], their associated types are all not function types, so they should be declarations of objects. Namely, they should be called variables, Right?
Whether the rule [dcl.dcl#dcl.pre-10] still applies to parameter declaration and member-declaration?
Moreover, why the definition for variable only qualifies reference type that it shall not be a reference of a non-static member of a class? why not give a similar qualification for an object? Maybe a non-static data member other than a reference type is not an object at all? If it was, when will we call a a member subobject?
Please interpret these issues.
The standard is generally confused about the difference between a variable (a kind of entity that can, for instance, be found by name lookup) and an object (a unit of data that exists for some portion of the program’s execution). A non-reference variable with static storage duration has exactly one object associated with it—not counting tricks like explicit destructor calls and placement new—but a thread-local variable can have more than one object associated with it, and a (non-reference) variable with automatic storage duration has zero or more based on how many times its function or block has been reentered (on however many threads) at any moment (plus any number of suspended coroutine frames as of C++20). Non-static data members (again, that are not references) are even more arbitrary: there is an object for each for each object of the containing type, including all the cases above as well as those with dynamic storage duration. (For variables and non-static data members of reference type, all the above holds mutatis mutandis, but we don’t have a good, separate term for “an instance of a reference-type variable”.)
The situation is even worse with base classes: if D inherits from B and C which each inherit from A, D has three base classes but each D object has four base class subobjects. We don’t have a good term for “appearance of a class in the inheritance lattice of another” to capture that four-fold aspect without regard to a particular object, even though we need such a term for handling things like name lookup ambiguity.
Given the above, it’s hardly surprising that the answer to your question is going to be unsatisfactory, but here it is: a non-static data member (a “member variable”) is never a variable even though there can be many (sub)objects associated with it. It can be a reference, just like extern int &r; (which is also not an object), which is why the definition in [basic.pre] has to exclude non-static data members when it considers “references” (by which we mean certain entities) and “objects” (by which we mean certain other entities).

Comparison of member initialization by constructor vs. direct initialization

I'm creating a class in which one member is a const pointer (immutable address) to another member of the struct.
In the simplified version below, will both classes always behave the same value? Especially in the sense of whether the addresses stored in ptr are guaranteed to be properly initialized.
struct First
{
int a;
int* const ptr = &a;
};
struct Second
{
int a;
int* const ptr;
Second() : ptr(&a) {}
};
(In my actual application the member a is a class instance, and ptr is replaced by a map from some enums to pointers pointing to members of a.)
In the simplified version below, will both structs always behave the same way?
No they won't, but it may be ok for your case. Read on.
Both First::ptr and Second::ptr will be initialized to the expected value being the address of First::a and respectively Second::a, but:
[class.mem]/7 & [class.mem]/9
7 In a member-declarator, an = immediately following the declarator is interpreted as introducing a pure-specifier
if the declarator-id has function type, otherwise it is interpreted as introducing a brace-or-equal-initializer.
9 A brace-or-equal-initializer shall appear only in the declaration of a data member. (For static data members,
see 12.2.3.2; for non-static data members, see 15.6.2 and 11.6.1). A brace-or-equal-initializer for a non-static
data member specifies a default member initializer for the member, and shall not directly or indirectly cause
the implicit definition of a defaulted default constructor for the enclosing class or the exception specification
of that constructor.
This means, First has a defaulted default constructor where Second has a user-provided default constructor, which change some characteristic of those classes. I can for instance think of aggregates, triviality and maybe standard layouts.

Why can a const member function modify a static data member?

In the following C++ program, modifying a static data member from a const function is working fine:
class A
{
public:
static int a; // static data member
void set() const
{
a = 10;
}
};
But modifying a non-static data member from a const function does not work:
class A
{
public:
int a; // non-static data member
void set() const
{
a = 10;
}
};
Why can a const member function modify a static data member?
It's the rule, that's all. And for good reason.
The const qualifier on a member function means that you cannot modify non-mutable non-static class member variables.
By way of offering some rationalisation, the this pointer in a const qualified member function is a const type, and this is inherently related to an instance of a class. static members are not related to a class instance. You don't need an instance to modify a static member: you can do it, in your case, by writing A::a = 10;.
So, in your first case, think of a = 10; as shorthand for A::a = 10; and in the second case, think of it as shorthand for this->a = 10;, which is not compilable since the type of this is const A*.
According to the C++ Standard (9.2.3.2 Static data members)
1 A static data member is not part of the subobjects of a class...
And (9.2.2.1 The this pointer)
1 In the body of a non-static (9.2.1) member function, the keyword
this is a prvalue expression whose value is the address of the object
for which the function is called. The type of this in a member
function of a class X is X*. If the member function is declared
const, the type of this is const X*,...
And at last (9.2.2 Non-static member functions)
3 ... if name lookup (3.4) resolves the name in the id-expression to a
non-static non-type member of some class C, and if either the
id-expression is potentially evaluated or C is X or a base class of X,
the id-expression is transformed into a class member access expression
(5.2.5) using (*this) (9.2.2.1) as the postfix-expression to the
left of the . operator.
Thus in this class definition
class A
{
public:
static int a;
void set() const
{
a = 10;
}
};
the static data member a is not a subobject of an object of the class type and the pointer this is not used to access the static data member. So any member function, non-static constant or non-constant, or a static member function can change the data member because it is not a constant.
In this class definition
class A
{
public:
int a;
void set() const
{
a = 10;
}
};
the non-static data member a is an subobject of an object of the class type. To access it in a member function there is used either a member access syntax of this syntax is implied. You may not use a constant pointer this to modify the data member. And the pointer this is indeed has type const A * within the function set because the function is declared with the qualifier const. If the function had no the qualifier in this case the data member could be changed.
The thing is, that if a member function of a class A is const, then the type of this is const X*, and thereby prevents non-static data members from being altered (cf, for example, C++ standard):
9.3.2 The this pointer [class.this]
In the body of a non-static (9.3) member function, the keyword this is a prvalue expression whose
value is the address of the object for which the function is called.
The type of this in a member function of a class X is X*. If the
member function is declared const, the type of this is const X*, ...
If a is a non-static data member, then a=10 is the same as this->a = 10, which is not allowed if the type of this is const A* and a has not been declared as mutable. Thus, since void set() const makes the type of this being const A*, this access is not allowed.
If a is a static data member, in contrast, then a=10 does not involve this at all; and as long as static int a by itself has not been declared as const, statement a=10 is allowed.
The const qualifier on a member function means that you cannot modify non-mutable, non-static class data members.

Is the cppreference definition of non-static data member wrong?

Definition from cppreference:
Non-static data members are the variables that are declared in a member specification of a class.
And they have the example:
class S
{
int& r; // non-static data member of reference type
};
But we know that non-static data member references are not variables because of the Standard:
§3/6: A variable is introduced by the declaration of a reference other than a non-static data member or of an object.
So is their definition of non-static data member wrong (they forgot about this exception)? Where I can find correct definition of the term "non-static data member"?
Unfortunately I couldn't find a definition of non-static data member in the C++ Standard.
EDIT: From cppreference object definition and discussion below we can conclude that non-static data members are not objects at all. And cppreference non-static member page corrected the discussed definition at the moment.
So their definition of non-static data member is wrong
Yes, it was wrong to use the word "variable" in the introductory sentence of the data members page (and, as mentioned in the comment, it's a wiki, the discussion tabs on wiki pages get faster feedback).
The current standard wording is 3[basic]/6 and :
A variable is introduced by the declaration of a reference other than a non-static data member or of an object. The variable’s name, if any, denotes the reference or object.
So, reference data members are excluded explicitly, and to figure out the rest you need the definition of "object" from 1.8[intro.object]/1
An object is created by a definition (3.1), by a new-expression (5.3.4), when implicitly changing the active member of a union (9.3), or when a temporary object is created (4.4, 12.2).
And finally 3.1[basic.def]/2
A declaration is a definition unless ... it declares a non-inline static data member in a class definition (9.2, 9.2.3),
Although it may seem like the distinction between variables and data members is impractical language-lawyerism, it is actually important when understanding compiler diagnostics, at least in this case:
struct X {
int m;
void f() { auto l = [m](){ return m; }; }
};
gcc:
error: capture of non-variable 'X::m'
clang:
error: 'm' in capture list does not name a variable
icc:
error: member "X::m" is not a variable

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.