Comparison of member initialization by constructor vs. direct initialization - c++

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.

Related

Can const-default-constructible objects be of non-class types?

Per my understanding, for class type T to be const-default-constructible type, the default-initialization of T shall invoke a user-provided constructor, or T shall provide a default member initializer for each non-variant non-static data member: ([dcl.init]/7)
A class type T is const-default-constructible if
default-initialization of T would invoke a user-provided constructor
of T (not inherited from a base class) or if
(7.4) each direct
non-variant non-static data member M of T has a default member
initializer [..]
Noting the bold part, it seems to me that const-default-constructible types can only be class types (including unions and structs). Therefore I can't say that const int, for example, is const-default-constructible type since int is not a class type.
You might ask, from where this confusion comes. Basically, [class.default.ctor]/2 says:
A defaulted default constructor for class X is defined as deleted
if:
[..]
(2.4) any non-variant non-static data member of const-qualified type (or array thereof) with no brace-or-equal-initializer is not
const-default-constructible ([dcl.init]),
[..]
Notice the word "any". This bullet considers cases where X has data member M, and M is of class types as well as non-class types. But per [dcl.init]/7, the const-default-constructible types are limited to only be class types.
Consider the following example,
struct S
{
const int I;
// Is the type of S::I said to be non-const-default-constructible?
S() = default;
};
Is there missing wording? Am I misreading the quotes?
PS: Thanks to #463035818_is_not_a_number for clarifying this. Consider this case:
struct X
{
const int M = 0;
// Is the type of X::M said to be const-default-constructible?
X() = default;
};
Is the type of S::I said to be non-const-default-constructible?
Yes. Like you've quoted in [dcl.init]/7 only class types can be const-default-constructible. The reason for this is non-class types do not have a default constructor, meaning they have no default value that can be used if they are declared like
const T foo;
When you read
any non-variant non-static data member of const-qualified type (or array thereof) with no brace-or-equal-initializer is not const-default-constructible ([dcl.init])
It is saying that if you have a member in the form of const T name; in your class then the default constructor is deleted if T is not const-default-constructible. In your case that means your constructor is deleted because const int is not const-default-constructible.
In the case of X, M is still not const-default-constructible because it is not a class type. X though is const-default-constructible because M has a brace-or-equal-initializer so [class.default.ctor]/2.4 does not apply.
This can be boiled down into a simple rule: All const objects must have an initializer
Since built in types do not get default initialized they must have a value provided by the programmer.
Non-class types are never const-default-constructible as you have read correctly.
But I don't think there is any wording missing. It seems intentional. The whole point of const-default-constructible is to make sure that you can't accidentally leave an object with an indeterminate value after initialization when you can't change that object's value later.
It would be UB to try to change the value of I after initialization (because it is declared const), but with the defaulted default constructor or without an explicit initializer on I, it would be default-initialized to an indeterminate value (because that is the effect of default-initialization on non-class types) which then is impossible to change. An indeterminate value can be used for almost nothing. The standard doesn't even allow copying it (except for types unsigned char and std::byte).
So there is no reason to allow you to default-construct this S with the defaulted default constructor.
(Technically the standard now allows using placement-new to replace the I object transparently as long as it is not part of a const-complete object, which allows changing its value in some sense, but that shouldn't be a normal use case.)

Why doesn't defining array without size with initializer work inside class definition? [duplicate]

Consider the code:
struct Foo
{
const char str[] = "test";
};
int main()
{
Foo foo;
}
It fails to compile with both g++ and clang++, spitting out essentially
error: array bound cannot be deduced from an in-class initializer
I understand that this is what the standard probably says, but is there any particular good reason why? Since we have a string literal it seems that the compiler should be able to deduce the size without any problem, similarly to the case when you simply declare an out-of-class const C-like null terminated string.
The reason is that you always have the possibility to override an in-class initializer list in the constructor. So I guess that in the end, it could be very confusing.
struct Foo
{
Foo() {} // str = "test\0";
// Implementing this is easier if I can clearly see how big `str` is,
Foo() : str({'a','b', 'c', 'd'}) {} // str = "abcd0"
const char str[] = "test";
};
Notice that replacing const char with static constexpr char works perfectly, and probably it is what you want anyway.
As mentioned in the comments and as answered by #sbabbi, the answer lies in the details
12.6.2 Initializing bases and members [class.base.init]
In a non-delegating constructor, if a given non-static data member or
base class is not designated by a mem-initializer-id (including the
case where there is no mem-initializer-list because the constructor
has no ctor-initializer) and the entity is not a virtual base class of
an abstract class (10.4), then
if the entity is a non-static data member that has a brace-or-equal-initializer , the entity is initialized as specified in
8.5;
otherwise, if the entity is an anonymous union or a variant member (9.5), no initialization is performed;
otherwise, the entity is default-initialized
12.6.2 Initializing bases and members [class.base.init]
If a given non-static data member has both a
brace-or-equal-initializer and a mem-initializer, the initialization
specified by the mem-initializer is performed, and the non-static data
member’s brace-or-equal-initializer is ignored. [ Example: Given
struct A {
int i = /∗ some integer expression with side effects ∗/ ;
A(int arg) : i(arg) { }
// ...
};
the A(int) constructor will simply initialize i to the value of arg,
and the side effects in i’s brace-or equal-initializer will not take
place. — end example ]
So, if there is a non-deleting constructor, the brace-or-equal-initializer is ignored, and the constructor in-member initialization prevails. Thus, for array members for which the size is omitted, the expression becomes ill-formed. §12.6.2, item 9, makes it more explicit where we it specified that the r-value initializer expression is omitted if mem-initialization is performed by the constructor.
Also, the google group dicussion Yet another inconsitent behavior in C++, further elaborates and makes it more lucid. It extends the idea in explaining that brace-or-equal-initializer is a glorified way of an in-member initialization for cases where the in-member initialization for the member does not exist. As an example
struct Foo {
int i[5] ={1,2,3,4,5};
int j;
Foo(): j(0) {};
}
is equivalent to
struct Foo {
int i[5];
int j;
Foo(): j(0), i{1,2,3,4,5} {};
}
but now we see that if the array size was omitted, the expression would be ill-formed.
But then saying that, the compiler could have supported the feature for cases when the member is not initialized by in-member constructor initialization but currently for the sake of uniformity, the standard like many other things, does not support this feature.
If the compiler was allowed to support what you described, and the size of str was deduced to 5,
Foo foo = {{"This is not a test"}};
will lead to undefined behavior.

Are pointer to a member of this allowed in object initialization?

From Aggregate initialization, set pointer to struct member, is the following code legal:
struct S
{
int a;
int* aptr;
};
S s = { 3, &s.a };
Quote from latest standard draft:
[basic.scope.pdecl]
The point of declaration for a name is immediately after its complete declarator ([dcl.decl]) and before its initializer (if any), except as noted below.
So, yes. The identifier s has already been declared, so it can be used in its initialiser.
Note that the value of s may not be used until it has been initialised. The value is not used in the example, so this is not a problem.
I'd also be curious about whether analogous code is valid when the two members of S are in reversed order
The order of members does not matter.

Active member of an union, uniform initialization and constructors

As the (Working Draft of) C++ Standard says:
9.5.1 [class.union]
In a union, at most one of the non-static data members can be active at any time, that is, the value of at most one of the non-static data members can be stored in a union at any time. [...] The size of a union is sufficient to contain the largest of its non-static data members. Each non-static data member is allocated as if it were the sole member of a struct. All non-static data members of a union object have the same address.
But I don't know how to identify which is the active member of an union and I'm not used enough to dive into the standard to locate what the standard says about it, I've tried to figure how the active member is setted but I've found how it is swapped:
9.5.4 [class.union]
[ Note: In general, one must use explicit destructor calls and placement new operators to change the active member of a union. —end note ] [Example: Consider an object u of a union type U having non-static data members m of type M and n of type N. If M has a non-trivial destructor and N has a non-trivial constructor (for instance, if they declare or inherit virtual functions), the active member of u can be safely switched from m to n using the destructor and placement new operator as follows:
u.m.~M();
new (&u.n) N;
—end example ]
So my guess is that the active member of an union is the one first asigned, used, constructed or placement-new'ed; but this becomes kind of tricky with uniform initialization, consider the following code:
union Foo
{
struct {char a,b,c,d;};
char array[4];
int integer;
};
Foo f; // default ctor
std::cout << f.a << f.b << f.c << f.d << '\n';
Which is the active member of the union on the code above? Is std::cout reading from the active member of the union? What about the code below?
Foo f{0,1,2,3}; // uniform initialization
std::cout << f.a << f.b << f.c << f.d << '\n';
With the lines above we can initialize the nested anonymous struct or either the array, if I provide only an integer I can initialize Foo::a or Foo::array or Foo::integer... which one would be the active member?
Foo f{0}; // uniform initialization
std::cout << f.integer << '\n';
I guess that the active member would be the aninymous struct in all of the above cases but I'm not sure.
If I want to activate one or the other union member, should I provide a constructor activating it?
union Bar
{
// #1 Activate anonymous struct
Bar(char x, char y, char z, char t) : a(x),b(y),c(z),d(t) {}
// #2 Activate array
Bar(char (&a)[4]) { std::copy(std::begin(a), std::end(a), std::begin(array)); }
// #3 Activate integer
Bar(int i) : integer(i) {}
struct {char a,b,c,d;};
char array[4];
int integer;
};
I'm almost sure that #1 and #3 will mark as active union the anonymous struct and the integer but I don't know about the #2 because in the moment we reach the body of the constructor the members are already constructed! so are we calling std::copy over an inactive union member?
Questions:
Which are the active union members of Foo if it is constructed with the following uniform initialization:
Foo{};
Foo{1,2,3,4};
Foo{1};
In the #2 constructor of Bar the Bar::array is the active union member?
Where in the standard can I read about which is exactly the active union member and how to set it without placement new?
Your concern about the lack of a rigorous definition of the active member of a union is shared by (at least some of) the members of the standardization committee - see the latest note (dated May 2015) in the description of active issue 1116:
We never say what the active member of a union is, how it can be changed, and so on. [...]
I think we can expect some sort of clarification in future versions of the working draft. That note also indicates that the best we have so far is the note in the paragraph you quoted in your question, [9.5p4].
That being said, let's look at your other questions.
First of all, there are no anonymous structs in standard C++ (only anonymous unions); struct {char a,b,c,d;}; will give you warnings if compiled with reasonably strict options (-std=c++1z -Wall -Wextra -pedantic for Clang and GCC, for example). Going forward, I'll assume we have a declaration like struct { char a, b, c, d; } s; and everything else is adjusted accordingly.
The implicitly defaulted default constructor in your first example doesn't perform any initialization according to [12.6.2p9.2]:
In a non-delegating constructor, if a given potentially constructed
subobject is not designated by a mem-initializer-id (including the
case where there is no mem-initializer-list because the constructor
has no ctor-initializer), then
(9.1) - if the entity is a non-static data member that has a brace-or-equal-initializer and either
(9.1.1) - the constructor’s class is a union (9.5), and no other variant member of that union is designated by a mem-initializer-id or
(9.1.2) - the constructor’s class is not a union, and, if the entity is a member of an anonymous union, no other member of that union is designated by a mem-initializer-id,
the entity is initialized as specified in 8.5;
(9.2) - otherwise, if the entity is an anonymous union or a variant member (9.5), no initialization is performed;
(9.3) - otherwise, the entity is default-initialized (8.5).
I suppose we could say that f has no active member after its default constructor has finished executing, but I don't know of any standard wording that clearly indicates that. What can be said in practice is that it makes no sense to attempt to read the value of any of f's members, since they're indeterminate.
In your next example, you're using aggregate initialization, which is reasonably well-defined for unions according to [8.5.1p16]:
When a union is initialized with a brace-enclosed initializer, the
braces shall only contain an initializer-clause for the first
non-static data member of the union. [ Example:
union u { int a; const char* b; };
u a = { 1 };
u b = a;
u c = 1; // error
u d = { 0, "asdf" }; // error
u e = { "asdf" }; // error
— end example ]
That, together with brace elision for the initialization of the nested struct, as specified in [8.5.1p12], makes the struct the active member. It answers your next question as well: you can only initialize the first union member using that syntax.
Your next question:
If I want to activate one or the other union member, should I provide a constructor activating it?
Yes, or a brace-or-equal-initializer for exactly one member according to [12.6.2p9.1.1] quoted above; something like this:
union Foo
{
struct { char a, b, c, d; } s;
char array[4];
int integer = 7;
};
Foo f;
After the above, the active member will be integer. All of the above should also answer your question about #2 (the members are not already constructed when we reach the body of the constructor - #2 is fine as well).
Wrapping up, both Foo{} and Foo{1} perform aggregate initialization; they're interpreted as Foo{{}} and Foo{{1}}, respectively, (because of brace elision), and initialize the struct; the first one sets all the struct members to 0 and the second one sets the first member to 1 and the rest to 0, according to [8.5.1p7].
All standard quotes are from the current working draft, N4527.
Paper N4430, which deals with somewhat related issues, but hasn't been integrated into the working draft yet, provides a definition for active member:
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]).
This effectively passes the buck to the definition of lifetime in [3.8], which also has a few issues open against it, including the aforementioned issue 1116, so I think we'll have to wait for several such issues to be resolved in order to have a complete and consistent definition. The definition of lifetime as it currently stands doesn't seem to be quite ready.
The active member is the last member you wrote to. Simple as that.
The term is not defined by C++ because it is defined by English.

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.