Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

list initialization of aggregates: when can it invoke copy constructor?

Consider the following code:

struct A {
  int x;
};

int main() {
  A a;
  A b{a};
}

Is this program well-formed at C++11 standard? In my copy of N3797 it says

8.5.4 List initialization [dcl.init.list]

3: List-initialization of an object or reference of type T is defined as follows:
- If T is an aggregate, aggregate initialization is performed (8.5.1).
- Otherwise, if T is a specialization of std::initializer_list<E>, ...
- Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen using overload resolution. If a narrowing conversion is required to convert any of the types, the program is ill-formed.
- Otherwise, if the initializer list has a single element of type E and either T is not a reference type or it is reference-related to E, the object or reference is initialized from that element; if a narrowing conversion is required to convert the element to T, the program is ill-formed.
- Otherwise, if T is a reference type, a pr-value temporary of the type reference by T is copy-list-initialized or direct-list-initialized, depending on the kind of initialization for the reference, and the reference is bound to that temporary.
- Otherwise, if the initializer list has no elements, the object is value-initialized.
- Otherwise, the program is ill-formed.

The point of the example is, the type is an aggregate, but list-initialization is supposed to invoke the copy constructor. On gcc 4.8 and gcc 4.9, at C++11 standard, it fails:

main.cpp: In function ‘int main()’:
main.cpp:7:8: error: cannot convert ‘A’ to ‘int’ in initialization
   A b{a};
        ^

and says A is not convertible to int or similar, because aggregate initialization fails. On gcc 5.4, it works fine at C++11 standard.

On clang you get similar errors with clang-3.5, 3.6, and it starts working at clang-3.7.

I understand that it is well-formed at C++14 standard, and that it was mentioned in a defect report here.

However, what I don't understand is why this was considered a defect in the standard.

When the standard writes,

"If X, foo-initialization is performed. Otherwise, if Y, bar-initialization is performed, .... Otherwise, the program is ill-formed.",

doesn't this mean that if X holds, but foo-initialization cannot be performed, then we should check if Y holds, and then attempt bar-initialization?

This would make the example work, because when aggregate initialization fails, we don't match std::initializer_list, and the next condition we match is "T is a class type", and then we consider constructors.

Note that this does seem to be how it works in this modified example

struct A {
  int x;
};

int main() {
  A a;
  const A & ref;
  A b{ref};
}

All the same compilers treat this the same way as the earlier example, at C++11 and C++14 standards. But it seems that the modified wording from the CWG defect record doesn't apply to this case. It reads:

If T is a class type and the initializer list has a single element of type cv T or a class type derived from T, the object is initialized from that element.

http://open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#1467

But in the second code example, the initializer list technically contains const T &. So I don't see how it would work unless after aggregate initialization fails, we are supposed to attempt constructors.

Am I wrong? Is it not supposed to attempt constructors after aggregate initialization fails?

Here's a related example:

#include <iostream>

struct B {
  int x;

  operator int() const { return 2; }
};

int main() {
  B b{1};
  B c{b};
  std::cout << c.x << std::endl;
}

In clang-3.6, gcc-4.8, gcc-4.9, it prints 2, and in clang-3.7, gcc-5.0 it prints 1.

Assuming I'm wrong, and at C++11 standard, list initialization of an aggregate is supposed to be aggregate initialization and nothing else, until the new wording in the defect report is introduced, is it a bug that this happens even when I select -std=c++11 on the newer compilers?

like image 316
Chris Beck Avatar asked Aug 17 '16 20:08

Chris Beck


1 Answers

When the standard writes,

"If X, foo-initialization is performed. Otherwise, if Y, bar-initialization is performed, .... Otherwise, the program is ill-formed.",

doesn't this mean that if X holds, but foo-initialization cannot be performed, then we should check if Y holds, and then attempt bar-initialization?

No, it does not. Think of it like actual code:

T *p = ...;
if(p)
{
  p->Something();
}
else
{ ... }

p isn't NULL. That doesn't mean it's a valid pointer either. If p points to a destroyed object, p->Something() failing will not cause you to skip to the else. You had your chance to protect the call in the condition.

So you get undefined behavior.

The same goes here. If X, do A. That doesn't imply what happens if A fails; it tell you to do it. If it can't be done... you're screwed.

like image 158
Nicol Bolas Avatar answered Dec 15 '22 06:12

Nicol Bolas