Carousel Carousel - 4 months ago 14
C++ Question

Does C++ language enforce compiler optimization when using list initialization?

Here is the source.cpp

#include <iostream>

struct A {
A(int i) : i(i) { std::cout << this << ": A(int)" << std::endl; }
A(A const &a) : i(a.i) { std::cout << this << ": A(A const &)" << std::endl; }
A(A &&a) : i(a.i) { std::cout << this << ": A(A &&)" << std::endl; }
~A() { std::cout << this << ": ~A()" << std::endl; }

private:
int i;
};

int main() {
std::cout << "#1 :" << std::endl;
A a1 = 1; // #1, copy-initialization

std::cout << "#2 :" << std::endl;
A a3(1); // #2, direct-initialization

std::cout << "#3 :" << std::endl;
A a4 = {1}; // #3, copy-list-initialization

std::cout << "#4 :" << std::endl;
A a5{1}; // #4, direct-list-initialization

std::cout << std::endl;
return 0;
}


Compiling above codes with
clang++ -std=c++14 -Wall -fno-elide-constructors -pedantic -o main.exe source.cpp
(Here, I disable the construction optimization. BTW, I'm using Clang 3.8.1). Then, I get following output:

#1 :
0x61fe40: A(int)
0x61fe48: A(A &&)
0x61fe40: ~A()
#2 :
0x61fe30: A(int)
#3 :
0x61fe28: A(int)
#4 :
0x61fe20: A(int)

0x61fe20: ~A()
0x61fe28: ~A()
0x61fe30: ~A()
0x61fe48: ~A()


What supprise me is #3 doesn't call
A::A(int)
first and then
A::A(A &&)
as #1 does, although both of they are copy-initialized. I also tested it with gcc 6.1.0. Same thing happens. As far as I know, one of common usage of list initialization is to prohibit narrowing conversions. I didn't know it has anything related to compiling optimization. So,

Does C++ language enforce compiler optimization when using list initialization or just compilers prefer to do so or something else cause the behavior described above?

Answer

Direct- and copy-list-initialization both result in a call to a constructor in this case.

Using a process of elimination in the rules in [dcl.init.list] / 3,

List-initialization of an object or reference of type T is defined as follows:

[...]

Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed.

The important thing to take home is that copy-initialization and copy-list-initialization aren't equivalent, you can use copy-list-initialization to initialize an object with deleted copy and move constructors for instance:

struct A
{
  A(int i){}
  A(A const&) =delete;
  A(A&&) =delete;
};    

int main()
{
  A a1 = {1};
  A a2 = 1; // won't compile
}