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

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

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).

[[maybe_unused]] applied to static data members

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.

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

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];
};

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.

Order of evaluation

I wonder if construction like this (initialization list) has well defined EO (evaluation order):
struct MemoryManager
{
Pair* firstPair_;//<-beg
Pair* currentPair_;
Pair* lastPair_;//<-end
MemoryManager():lastPair_(currentPair_ = firstPair_ = nullptr)
{/*e.b.*/}
};
If yes I personally would prefer this way to the more conventional:
MemoryManager():firstPair_(nullptr),
currentPair_(nullptr),
lastPair_(nullptr)
{/*e.b*/}
As John Dibling has remarked, your construct is technically correct for the given concrete example, but it's brittle and it's hard to understand for many programmers.
Brittleness:
Can fail if order of declaration is changed.
Can fail if the init list is changed.
To evaluate such constructs on your own, keep this idea foremost in your mind: code is not about instructing the compiler to do your bidding, it is about communicating your intent to others (and perhaps your later self).
Hence, try to write clear code.
Cheers & hth.,
Yes. As shown in your code, the members will be initialized in the same order they are declared in the struct/class definition (the order of initializers in the constructor definition is irrelevant, at best you will get a warning telling you they are in an incorrect order).
12.6.2 §5: Then, nonstatic data members shall be initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).
Note that this is only true for variables that are part of the same access specifier, so for instance variables found in a public: specifier may be initialized before or after those found in a private: specifier (the struct counts as a public: specifier, of course).
EDIT: the above paragraph was incorrect, I was thinking of allocation, not initialization:
9.2 §12: Nonstatic data members of a (non-union) class declared without an intervening access-specifier are allocated so that later members have higher addresses within a class object. The order of allocation of nonstatic data members separated by an access-specifier is unspecified (class.access.spec).
However, the more conventional way has a reason to exist, namely that if the order of declaration of the variables changes (for instance, due to refactoring), the code will not break silently. People don't readily assume the order of declaration is relevant, unless a warning tells them otherwise.
If you want to do this, then do it the way everybody understands immediately without having to browse the standard:
MemoryManager()
// no initialization here
{
lastPair_ = currentPair_ = firstPair_ = nullptr;
}
However, I don't really see what this buys you over
MemoryManager()
: lastPair_(), currentPair_(), firstPair_()
{}
which does exactly the same in only about half a dozen more characters.
For this specific example, the initialization order for members is irrelevant. The following constructor would have the same behaviour as the one in the question:
MemoryManager():firstPair_(lastPair_ = currentPair_ = nullptr)
{/*e.b.*/}
This is because the members are POD and so are not default initialized by the constructor at all (12.6.2/4 C++ '03):
If a given nonstatic data member or base class is not named by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer), then
If the entity is a nonstatic data member of (possibly cv-qualified) class type (or array thereof) or a base class, and the entity class is a non-POD class, the entity is default-initialized (8.5). If the entity is a non- static data member of a const-qualified type, the entity class shall have a user-declared default constructor.
Otherwise, the entity is not initialized. If the entity is of const-qualified type or reference type, or of a (possibly cv-qualified) POD class type (or array thereof) containing (directly or indirectly) a member of a const-qualified type, the program is ill-formed.
For the raw pointer members above, the 'otherwise' bullet applies.
Now, even if the members did have class type, say:
class MbrType {
public:
MbrType();
MbrType(int *);
MbrType(MbrType const &);
MbrType & operator=(MbrType const &);
};
Then, the constructor as you've written it would result in the members having the values that you expect, but a non optimizing compiler would be allowed to implement your constructor as:
MemoryManager()
: firstPair_ () // implicit call to default constructor
, currentPair_ () // implicit call to default constructor
, lastPair_(currentPair_.operator=(firstPair_.operator=(MbrType (nullptr))))
{/*e.b.*/}
Resulting in 6 calls rather than 3.
No, but it doesn't matter. Your code does not depend on the order in which currentPair_ and firstPair_ are zeroed.