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
Tis defined as follows:
- IfTis an aggregate, aggregate initialization is performed (8.5.1).
- Otherwise, ifTis a specialization ofstd::initializer_list<E>, ...
- Otherwise, ifTis 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 typeEand eitherTis not a reference type or it is reference-related toE, the object or reference is initialized from that element; if a narrowing conversion is required to convert the element toT, the program is ill-formed.
- Otherwise, ifTis a reference type, a pr-value temporary of the type reference byTis 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
Tis a class type and the initializer list has a single element of typecv Tor a class type derived fromT, 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, .... 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With