Consider the following program:
struct X
{
X(int, int) { }
X(X&&) { }
};
int main()
{
X x( {0, 1} ); // Doesn't compile on ICC 13.0.1, compiles on
// Clang 3.2, GCC 4.7.2, and GCC 4.8.0 beta.
}
When compiled with GCC 4.7.2, GCC 4.8.0, and Clang 3.2, this program does the following (*):
X
passing values 0
and 1
to the constructor, then;X
from that temporary.With ICC 13.0.1, instead, it doesn't compile.
QUESTION #1: Who is right?
(*) Actually, the creation of the temporary and the call to the move constructor are elided, but compiling with the -fno-elide-constructors
option and adding some printouts to the constructors reveals that this is what is going on.
Now consider the following, slight variation of the above program, where uniform initialization is used to direct-initialize x
:
int main()
{
X x{ {0, 1} }; // ERROR! Doesn't compile.
// ^........^
}
I would not expect the use of braces instead of parentheses to change anything here, but it somehow does: this program doesn't compile on any of the compilers I've tested it on (Clang 3.2, GCC 4.7.2, GCC 4.8.0 beta, and ICC 13.0.1).
QUESTION #2: Why?
Uniform initialization is a feature in C++ 11 that allows the usage of a consistent syntax to initialize variables and objects ranging from primitive type to aggregates. In other words, it introduces brace-initialization that uses braces ({}) to enclose initializer values.
Problem of initialization in C++ Therefore, when objects are created, the members of the object cannot be initialized directly and this problem of not being able to initialize data members is known as the problem of initialization.
There are two ways to initialize a class object: Using a parenthesized expression list. The compiler calls the constructor of the class using this list as the constructor's argument list. Using a single initialization value and the = operator.
An object of type std::initializer_list<T> is a lightweight proxy object that provides access to an array of objects of type const T .
It's simply a bug in all the compilers. §8.5.4/3 says,
List-initialization of an object or reference of type T is defined as follows:
— If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
— Otherwise, if T is an aggregate, aggregate initialization is performed (8.5.1).
— Otherwise, if T is a specialization of std::initializer_list, an initializer_list object is constructed as described below and used to initialize the object …
— 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.
— Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary.
— Otherwise, if the initializer list has a single element, the object or reference is initialized from that element; if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed.
…
There are quite a few cases. Note that you're actually list-initializing an prvalue temporary object bound to the rvalue reference.
As for GCC, I think it's trying to apply the last item mentioned above, for a single-element initializer list. If I change the constructor signature to X(X&&, int = 3)
, then the initializer { {0, 1} }
fails but { {0, 1}, 3 }
succeeds. The single item should succeed because because the element is a braced-init-list, and I believe that case is supposed to allow extra enclosing braces, analogous to parens. But the failure is similar to other shortcomings of GCC with brace elision.
My high-level impression is that the problems arise when the compiler tries to treat the list as an object with a type, which it's not. It's hard to convert that back to something like a parenthesized argument list.
Looking more specifically at the error messages (thanks for the LWS link),
ICC insists it expects an expression. This is wrong because according to the basic grammar, braced-init-lists can enclose other braced-init-lists, not only expressions.
Clang says "candidate constructor not viable: cannot convert initializer list argument to 'X'" but it works if the conversion is explicit, using X x{ X{0, 0 } };
. That doesn't make sense. It's not a conversion, because a list doesn't have a type to convert from. It's list-initialization.
GCC says "no known conversion for argument 1 from '' to 'X&&'" suggesting it didn't even get to the point of defining a temporary to bind to the reference. As with Clang it seems to be attempting spurious conversion, and specifying X{0,0}
fixes it.
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