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:
- IfT
is an aggregate, aggregate initialization is performed (8.5.1).
- Otherwise, ifT
is a specialization ofstd::initializer_list<E>
, ...
- Otherwise, ifT
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 typeE
and eitherT
is 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, ifT
is a reference type, a pr-value temporary of the type reference byT
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 typecv T
or 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