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?


When the standard writes,

"If X , foo-initialization is performed. Otherwise, if Y , bar-initialization is performed, ...

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. If X holds we perform foo-initialization. If that fails the program is ill-formed.


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.

链接地址: http://www.djcxy.com/p/67794.html

上一篇: 单元测试与功能测试

下一篇: 列表聚合的初始化:什么时候可以调用复制构造函数?