Here is the code example where Test
is a non-copyable and non-moveable class with some virtual
members and a user-defined constructor, and B
is a class that contains a raw (C-style) array of Test
objects:
class Test
{
public:
Test() = delete;
Test(const Test&) = delete;
Test(Test&&) = delete;
Test& operator=(const Test&) = delete;
Test& operator=(Test&&) = delete;
Test(int a, int b) : a_(a), b_(b) {}
virtual ~Test() {}
int a_;
int b_;
};
//----------------
class B
{
public:
/*(1)*/ B() : test_{{1, 2}, {3, 4}} {} // Does not compile on GCC, but compiles on Clang and MSVC
private:
Test test_[2];
};
//----------------
int main()
{
B b;
/*(2)*/ Test test[2] = {{1, 2}, {3, 4}}; // Successfully compiles on GCC, Clang and MSVC
}
I want to initialize B
's internal array test_
using the braced initialization syntax (line /*1*/
), so that each of the two Test
objects would be constructed in-place, without needing to create a temporary and then move it.
On Clang and MSVC, this code compiles without warnings.
But GCC's behavior confuses me: it fails to compile the line /*1*/
, while successfully compiling the line /*2*/
, where I am using the same syntax to initialize a local array. Yet for compiling the first line, it still requires the deleted move constructor of class Test
.
The question is, why? Does the C++ standard clearly define whether these lines /*1*/
and /*2*/
should compile at all? If it does, which of the compilers is right from the standard's point of view? Can this inconsistent behavior be called a GCC bug, or do Clang and MSVC overlook some checks that they are supposed to perform?
I can understand that GCC might require a move constructor in order to create a temporary Test
object from the inner braces ({1, 2}
), and then move that object into an array. Hence the compilation error. But if that is so, why does it not fail on the line /*(2)*/
for the very same reason? This is the most confusing thing to me in this example.
By the way, here's an interesting observation: if I replace the definition of test_
with std::array<Test, 2>
(instead of a "C-style" array), and replace the code in the constructor's initialization list with test_{{{1, 2}, {3, 4}}}
, everything starts to compile successfully on all three mentioned compilers.
It is also unclear to me why does GCC not fail in this case on any line, while failing with a "raw" array.
Can anyone explain this as well?
I see no problem with the initialization, so I think this is a GCC bug.
The initialization involved is list-initialization, so we consult [dcl.init.list]/3:
List-initialization of an object or reference of type
T
is defined as follows:
[...]
(3.3) Otherwise, if
T
is an aggregate, aggregate initialization is performed.[...]
(An array is an aggregate.) Now we go to [dcl.init.aggr]/3:
When an aggregate is initialized by an initializer list as specified in [dcl.init.list], the elements of the initializer list are taken as initializers for the elements of the aggregate, in order. Each element is copy-initialized from the corresponding initializer-clause. If the initializer-clause is an expression and a narrowing conversion is required to convert the expression, the program is ill-formed.
So, for either of the two elements, we are effectively doing Test a = {1, 2}
, which is valid because Test(int, int)
is not explicit. Therefore, the initialization is well-formed and should be accepted by the compiler.
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