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).
Related
In the current version of the C++ standard draft, [basic.life]/1 states:
The lifetime of an object or reference is a runtime property of the
object or reference. A variable is said to have vacuous initialization
if it is default-initialized and, if it is of class type or a
(possibly multi-dimensional) array thereof, that class type has a
trivial default constructor. The lifetime of an object of type T
begins when:
storage with the proper alignment and size for type T is obtained, and
its initialization (if any) is complete (including vacuous
initialization) ([dcl.init]),
except that if the object is a union
member or subobject thereof, its lifetime only begins if that union
member is the initialized member in the union ([dcl.init.aggr],
[class.base.init]), or as described in [class.union]. [...]
From that paragraph I understand that the only way a member of a union begins its lifetime is if:
that member "is the initialized member in the union" (e.g. if it is referenced in a mem-initializer), or
some other way mentioned in [class.union]
However, the only normative paragraph in [class.union] that specifies how a union member can begin its lifetime is [class.union]/5 (but it only applies to specific types, i.e. either non-class, non-array, or class type with a trivial constructor that is not deleted, or array of such types).
The next paragraph, [class.union]/6 (comprising a note and an example, therefore it contains no normative text), describes a way to change the active member of a union, by using a placement new-expression, such as new (&u.n) N;, where
struct N { N() { /* non-trivial constructor */ } };
struct M { M() { /* non-trivial constructor */ } };
union
{
N n;
M m;
} u;
My question is where in the standard is it specified that new (&u.n) N; begins the lifetime of u.n?
Thank you!
An important rule regarding this is:
[class.union]/1
In a union, a non-static data member is active if its name refers to an object whose lifetime has begun and has not ended ([basic.life]). ...
As far as this rule is considered, the active member could change at any time a member object begins its lifetime. The rule [class.union]/5 further allows changing the active member also by assigning to a non-active member of a limited set of types. The lack of a separate rule for placement new by itself doesn't disallow changing the member. If it begins the lifetime of the member, then the member is the active member of the union.
So, [basic.life/1] says that the lifetime of the member begins only if [class.union] says so1, and [class.union/1] says that the member is active only if its lifetime has begun2. This does seem like a bit of a catch-22.
My best attempt at reading the rules in a way that makes sense is to interpret that placement-new begins the lifetime of the member, therefore [class.union/1] applies, and therefore "or as described in [class.union]" applies and therefore the highlighted exception doesn't apply. Next I would like to say therefore the lifetime begins, but that logic is circular.
The non-normative [class.union]/6 makes it quite clear that the placement new is intended to be allowed, but the normative rules are tangled. I would say that the wording could be improved.
1 (or when the union is initialised with that member, which isn't the case we are considering)
2 (or after assignment as per [class.union]/5, which isn't the case we are considering)
My question is where in the standard is it specified that new (&u.n) N; begins the lifetime of u.n?
Nowhere. Placement new creates a new object which becomes union member subobject per [intro.object]/2:
If an object is created in storage associated with a member subobject or array element e (which may or may not be within its lifetime), the created object is a subobject of e's containing object if:
— the lifetime of e's containing object has begun and not ended, and
— the storage for the new object exactly overlays the storage location associated
with e, and
— the new object is of the same type as e (ignoring cv-qualification).
C++ can't define unions at that point, because:
lvalues by definition must refer to an object
objects have a lifetime; it isn't clear what's a pre-lifetime object
in theory two unrelated objects can't be at the same address, but pre-lifetime objects can, so there is no such thing as pre-lifetime object
So mutable unions can't be well defined in C++, end of story.
In the c++ standard, in [basic.lval]/11.6 says:
If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:[...]
an aggregate or union type that includes one of the aforementioned types among its elements or non-static data members (including, recursively, an element or non-static data member of a subaggregate or contained union),[...]
This sentence is part of the strict-aliasing rule.
Can it allow us to access the inactive member of a non existing union? As in:
struct A{
int id :1;
int value :32;
};
struct Id{
int id :1;
};
union X{
A a;
Id id_;
};
void test(){
A a;
auto id = reinterpret_cast<X&>(a).id_; //UB or not?
}
Note: Bellow an explanation of what I do not grasp in the standard, and why the example above could be useful.
I wonder in what could [basic.lval]/11.6 be usefull.
[class.mfct.non-static]/2 does forbid us to call a member function of the "casted to" union or aggregate:
If a non-static member function of a class X is called for an object that is not of type X, or of a type derived from X, the behavior is undefined.
Considering that static data member access, or static member function can directly be performed using a qualified-name (a_class::a_static_member),
the only usefull uses case of the [basic.lval]/11.6, may be to access member of the "casted to" union. I thought about using this last standard rule to implement an "optimized variant". This variant could hold either a class A object or a class B object, the two starting with a bitfield of size 1, denoting the type:
class A{
unsigned type_id_ :1;
int value :31;
public:
A():type_id_{0}{}
void bar{};
void baz{};
};
class B{
unsigned type_id_ :1;
int value :31;
public:
B():type_id_{1}{}
int value() const;
void value(int);
void bar{};
void baz{};
};
struct type_id_t{
unsigned type_id_ :1;
};
struct AB_variant{
union {
A a;
B b;
type_id_t id;};
//[...]
static void foo(AB_variant& x){
if (x.id.type_id_==0){
reinterpret_cast<A&>(x).bar();
reinterpret_cast<A&>(x).baz();
}
else if (x.id.type_id_==1){
reinterpret_cast<B&>(x).bar();
reinterpret_cast<B&>(x).baz();
}
}
};
The call to AB_variant::foo does not invoke undefined behavior as long as its argument refers to an object of type AB_variant thanks to the rule of pointer-interconvertibility [basic.compound]/4. The access to the inactive union member type_id_ is allowed because id belongs to the common initial sequence of A, B and type_id_t [class.mem]/25:
But what happens if I try to call it with a complete object of type A?
A a{};
AB_variant::foo(reinterpret_cast<AB_variant&>(a));
The problem here is that I try to access an inactive member of a union that does not exist.
The two pertinent standard paragraphs are [class.mem]/25:
In a standard-layout union with an active member of struct type T1, it is permitted to read a non-static data member m of another union member of struct type T2 provided m is part of the common initial sequence of T1 and T2; the behavior is as if the corresponding member of T1 were nominated.
And [class.union]/1:
In a union, a non-static data member is active if its name refers to an object whose lifetime has begun and has not ended.
Q3: Does the expression "its name refers" signify that "an object" is actually an object built within a living union? Or could it refers to object a because of [basic.lval]/11.6.
[expr.ref]/4.2 defines what E1.E2 means if E2 is a non-static data member:
If E2 is a non-static data member [...], the
expression designates the named member of the object designated by the
first expression.
This defines behavior only for the case where the first expression actually designates an object. Since in your example the first expression designates no object, the behavior is undefined by omission; see [defns.undefined] ("Undefined behavior may be expected when this document omits any explicit definition of behavior...").
You are also misinterpreting what "access" means in the strict aliasing rule. It means "read or modify the value of an object" ([defns.access]). A class member access expression naming a non-static data member neither reads nor modifies the value of any object and therefore is not an "access", and therefore there's never an "access ... through" a glvalue of "an aggregate or union type" by reason of a class member access expression.
[basic.lval]/11.6 is essentially copied from C, where it actually meant something because assigning or copying a struct or union accesses the object as a whole. It's meaningless in C++ because assignment and copying of class types are performed through special member functions that either performs memberwise copying (and so "accesses" the members individually) or operates on the object representation. See core issue 2051.
There are many situations, especially involving type punning and unions, where one part of the C or C++ Standard describes the behavior of some action, another part describes an overlapping class of actions as invoking UB, and the area of overlap includes some actions which should be processed consistently by all implementations as well as others that would be impractical to support on at least some implementations. Rather than trying to fully describe all cases that should be treated as defined, the authors of the Standard expected that implementations would seek to uphold the Spirit of C described in the Rationale, including the principle "Don't prevent the programmer from doing what needs to be done". This would generally lead to quality implementations giving priority to the definition of behavior when necessary to meet their customer's needs, while giving priority to the "undefinition" of behavior when that would allow optimizations that also serve their customer's needs.
The only way to treat the C or C++ Standard as defining a useful language is to recognize a category of actions whose behavior is described by one part of the Standard and classified as UB by another, and recognizing the treatment of actions in that category as a Quality of Implementation issue outside the jurisdiction of the Standard. The authors of the Standard expected compiler writers to be sensitive to their customers' needs, and thus didn't see conflicts between behavioral definitions and undefinitions as a particular problem. They thus saw no need to define terms like "object", "lvalue", "lifetime", and "access" in ways that could be applied consistently without creating such conflicts, and the definitions they created are thus not usable for purposes of deciding whether or not particular actions should be defined when such conflicts exist.
Consequently, unless or until the Standards recognize more concepts associated with objects and ways of accessing them, the question of whether a quality implementation intended to be suitable for some purpose should be expected to support a certain action will depend upon whether its authors should be expected to recognize that the action would be useful for such purpose.
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.
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
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.