Can copy elision be perfomed in aggregate initialization in c++17? - c++

Given:
//C++17
#include <string>
struct Foo {
int i;
std::string str;
};
int main() {
Foo foo{1, std::string("Hello, world!")};
}
Can Foo::i and Foo::str be directly initialized from 1 and std::string(...) instead of being copied into them, and explain why can/can't using C++17 standard(probably some code for testing purpose)?
If they can't, how many copies are required?

Aggregate initialization basically performs element-wise copy-initialization. So this:
struct Foo {
int i;
std::string str;
};
Foo foo{1, std::string("Hello, world!")};
does the same initializations as:
int i = 1;
std::string str = std::string("Hello, world!");
And we have a new rule in C++17 that says that:
If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object. [ Example: T x = T(T(T())); calls the T default constructor to initialize x. — end example ]
which means that the second initialization must behave as if you'd written:
std::string str("Hello, world!");
That is, zero copies.
A nice demonstration of the new rule is the following example:
struct X {
X(int ) { }
X(X&& ) = delete;
};
struct Y {
X x;
};
int main() {
Y y{X{4}}; // ill-formed in C++14 due to deleted move ctor
// ok in C++17, no move required
}

Related

Default and zero initialization

#include <string>
 
struct T1 { int mem; };
 
struct T2
{
int mem;
T2() { } // "mem" is not in the initializer list
};
 
int n; // static non-class, a two-phase initialization is done:
// 1) zero initialization initializes n to zero
// 2) default initialization does nothing, leaving n being zero
 
int main()
{
int n; // non-class, the value is indeterminate
std::string s; // class, calls default ctor, the value is "" (empty string)
std::string a[2]; // array, default-initializes the elements, the value is {"", ""}
// int& r; // error: a reference
// const int n; // error: a const non-class
// const T1 t1; // error: const class with implicit default ctor
T1 t1; // class, calls implicit default ctor
const T2 t2; // const class, calls the user-provided default ctor
// t2.mem is default-initialized (to indeterminate value)
}
Im currently looking at the reference guide, however there a a few things i don't understand.
I have run the above code, for the struct T2, the data member "int mem" is not in the initialiser list. It is said that t2.mem is default-initialized to an indeterminate value.
But when i run this code, t2.mem seems to be zero initialised for me?
But when i run this code, t2.mem seems to be zero initialised for me?
No, in both cases (T1, T2), mem is not initialized, or initialize by indeterminate value. The explicit declaration of the constructor in this case does not affect the mem initialization. If you want to initialize mem, you must do it explicitly in the member initializer list:
struct T2
{
int mem;
T2() : mem(0) { }
};
Or through the default member initializer:
struct T1 { int mem = 0; };
Or through aggregate initialization ofT1, which is only takes place if T1 does not have any user declared constructors.
struct T1 { int mem; }
int main() {
T1 a{}; // aggregate initialization
assert(a.mem == 0)
T1 b; // default constructor does not value initialize mem
}
If you see that mem initialized by 0 in the second case, then this is most likely a feature of the compiler, or you just get lucky with the 0 value. This is not guaranteed by the standard, don't rely on it.

Initializing A Variable With Its Default Constructor

Question:
Is there a difference between the following initializations?
(A) What exactly is the second one doing?
(B) Is one more efficient than the other?
int variable = 0;
int variable = int();
This question also applies to other data types such as std::string:
std::string variable = "";
std::string variable = std::string();
Background:
I basically got the idea here (the second code sample for the accepted answer) when I was trying to empty out a stringstream.
I also had to start using it when I began learning classes and realized that member variable initializations had to be done in the constructor, not just following its definition in the header. For example, initializing a vector:
// Header.h
class myClass
{
private:
std::vector<std::string> myVector;
};
// Source.cpp
myClass::myClass()
{
for (int i=0;i<5;i++)
{
myVector.push_back(std::string());
}
}
Any clarity on this will be greatly appreciated!
Edit
After reading again, I realized that you explicitely asked about the default constructor while I provided a lot of examples with a 1 parameter constructor.
For Visual Studio C++ compiler, the following code only executes the default constructor, but if the copy constructor is defined explicit, it still complains because the never called copy constructor can't be called this way.
#include <iostream>
class MyInt {
public:
MyInt() : _i(0) {
std::cout << "default" << std::endl;
}
MyInt(const MyInt& other) : _i(other._i) {
std::cout << "copy" << std::endl;
}
int _i;
};
int main() {
MyInt i = MyInt();
return i._i;
}
Original (typo fixed)
For int variables, there is no difference between the forms.
Custom classes with a 1 argument constructor also accept assignment initialization, unless the constructor is marked as explicit, then the constructor call Type varname(argument) is required and assignment produces a compiler error.
See below examples for the different variants
class MyInt1 {
public:
MyInt1(int i) : _i(i) { }
int _i;
};
class MyInt2 {
public:
explicit MyInt2(int i) : _i(i) { }
int _i;
};
class MyInt3 {
public:
explicit MyInt3(int i) : _i(i) { }
explicit MyInt3(const MyInt3& other) : _i(other._i) { }
int _i;
};
int main() {
MyInt1 i1_1(0); // int constructor called
MyInt1 i1_2 = 0; // int constructor called
MyInt2 i2_1(0); // int constructor called
MyInt2 i2_2 = 0; // int constructor explicit - ERROR!
MyInt2 i2_3 = MyInt2(0); // int constructor called
MyInt3 i3_1(0); // int constructor called
MyInt3 i3_2 = 0; // int constructor explicit - ERROR!
MyInt3 i3_3 = MyInt3(0); // int constructor called, copy constructor explicit - ERROR!
}
The main difference between something like:
int i = int(); and int i = 0;
is that using a default constructor such as int() or string(), etc., unless overloaded/overridden, will set the variable equal to NULL, while just about all other forms of instantiation and declarations of variables will require some form of value assignment and therefore will not be NULL but a specific value.
As far as my knowledge on efficiency, neither one is "better".

Initializing std::array<char,x> member in constructor using string literal. GCC bug?

The following example initializing a std::array <char, N> member in a constructor using a string literal doesn't compile on GCC 4.8 but compiles using Clang 3.4.
#include <iostream>
#include <array>
struct A {
std::array<char, 4> x;
A(std::array<char, 4> arr) : x(arr) {}
};
int main() {
// works with Clang 3.4, error in GCC 4.8.
// It should be the equivalent of "A a ({'b','u','g','\0'});"
A a ({"bug"});
for (std::size_t i = 0; i < a.x.size(); ++i)
std::cout << a.x[i] << '\n';
return 0;
}
On first impression it looks like a GCC bug. I feel it should compile as we can initialize a std::array<char, N> directly with a string literal. For example:
std::array<char, 4> test = {"bug"}; //works
I would be interested to see what the Standard says about this.
Yes, your code is valid; this is a bug in gcc.
Here's a simpler program that demonstrates the bug (I've replaced std::array<char, 4> with S and got rid of A, as we can demonstrate the bug just in function return (this makes the analysis simpler, as we don't have to worry about constructor overloading):
struct S { char c[4]; };
S f() { return {"xxx"}; }
Here we have a destination object of type S that is copy-initialized (8.5p15) from the braced-init-list {"xxx"}, so the object is list-initialized (8.5p17b1). S is an aggregate (8.5.1p1) so aggregate initialization is performed (8.5.4p3b1). In aggregate initialization, the member c is copy-initialized from the corresponding initializer-clause "xxx" (8.5.1p2). We now return to 8.5p17 with destination object of type char[4] and initializer the string literal "xxx", so 8.5p17b3 refers us to 8.5.2 and the elements of the char array are initialized by the successive characters of the string (8.5.2p1).
Note that gcc is fine with the copy-initialization S s = {"xxx"}; while breaking on various forms of copy- and direct-initialization; argument passing (including to constructors), function return, and base- and member-initialization:
struct S { char c[4]; };
S f() { return {"xxx"}; }
void g(S) { g({"xxx"}); }
auto p = new S({"xxx"});
struct T { S s; T(): s({"xxx"}) {} };
struct U: S { U(): S({"xxx"}) {} };
S s({"xxx"});
The last is particularly interesting as it indicates that this may be related to bug 43453.

A BIG bug of VC++? Why does initializer-list not value-initialize a struct?

The C++11 standard 8.5.4.3 says:
"If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized."
struct A
{
int get() { return i; }
private:
int i;
};
int main()
{
A a = {};
int n = a.get();
cout << n << endl;
// n is a random number rather than 0
return 0;
}
Is this a bug of VC++? My VC++ is the latest Nov 2012 CTP.
Value-initialization of a non-aggregate class type is covered by 8.5p8. In your case the (non-union) class has an implicitly-declared defaulted default no-parameter constructor (12.1p5), which is not deleted and is trivial (ibid). Thus the second bullet of 8.5p8 applies:
— if T is a (possibly cv-qualified) non-union class type without a user-provided or deleted default constructor, then the object is zero-initialized and, if T has a non-trivial default constructor, default-initialized;
So A should be zero-initialized, and the program should print 0.
On the following program:
struct A { int get() { return i; } private: int i; };
#include <iostream>
int main() {
char c[sizeof(A)];
new (c) int{42};
std::cout << (new (c) A{})->get() << '\n';
}
gcc-4.7.2 correctly outputs 0; gcc-4.6.3 incorrectly outputs 42; clang-3.0 goes absolutely crazy and outputs garbage (e.g. 574874232).

initializer_list and default constructor overload resolution

#include <initializer_list>
#include <iostream>
using namespace std;
struct Y {};
struct X
{
X(initializer_list<Y>) { cout << "yay" << endl; }
explicit X() { cout << "boo" << endl; }
};
X f()
{
return {};
}
int main()
{
f();
return 0;
}
This prints out "boo". Why doesn't it print out "yay" ?
Is there anyway to differentiate the following two constructions:
X()
X{}
or
return X();
return {};
or
void g(const X&)
g(X())
g({})
Thanks.
Is there anyway to differentiate the following two constructions:
No. They are not different constructs.
The primary purpose of the {} constructor syntax was to introduce uniform initialization, to have initialization work the same everywhere. If there was a difference between them, it wouldn't be uniform.
If you want to use the empty initializer list constructor, you have to state that you're passing an initializer list explicitly. Like this: return initializer_list<Y>{}; Of course, one of the other purposes of uniform initialization is to not have to type out typenames so much, so you can achieve the same effect with return {{}};
return {}; will always use a default constructor if there is one.
return X({}); or return {{}}; will construct from an empty initialiser list.
It uses the default constructor because list initialization with {} is meant to be a short form of value initialization always, disregarding of other constructors, even if they are initializer list constructors.
Is there anyway to differentiate the following two constructions: ...
The X() is always value initialization, while X{} is only value initialization if X has a default constructor. If it is an aggregate, then X{} is aggregate initialization (initializing the members of X by a {} recursively). If it only has initializer list constructors and no default constructors, then X() is invalid and X{} could be valid
struct A { A(initializer_list<int>); };
A a = A{}; // valid
A b = A(); // invalid
Essentially, what X{} does depends on what X is. But X() always value initializes.
... or return X(); vs return {};
Something subtle to mention... In return {} the target is copy-list-initialized, while in return X(); first direct-initializes an X. But even though it is copy-list-initialized, it can use an explicit default constructor, because value initialization doesn't care about explicit. However when you do return {} and you try to use an explicit non-default constructor you will error out
struct A {
explicit A(initializer_list<int>);
};
A f() { return {}; } // error!
struct B {
explicit B();
};
B g() { return {}; } // OK
You could be a bit more explicit:
return initializer_list<Y>();