Please consider the following code:
class A {
private:
std::string s;
public:
A() = delete;
A(const A&) = delete;
A(A&&) = delete;
A(const std::string &a) : s(a) {}
};
Now, I would like to initialize an array of A using list initialization. g++ (4.9.1) could successfully build the following code:
int main() {
A arr[2] = {{"a"}, {"b"}};
return 0;
}
But, it failed for the following code:
class Aggr {
private:
A arr[2];
public:
Aggr() : arr{{"a"}, {"b"}} {}
};
The error messages are,
test.cc: In constructor ‘Aggr::Aggr()’:
test.cc:22:28: error: use of deleted function ‘A::A(A&&)’
Aggr() : arr{{"a"}, {"b"}} {}
^
test.cc:11:3: note: declared here
A(A&&) = delete;
^
That said, a list-initializer tries to call a move constructor for initializing an array inside of a class. That code, however, was successfully built by clang v3.5 without any warnings. So, I would like to know what the C++11 (or later version) specifies rules with respect to list-initialization. Thanks in advance.
Reading again and again the standard, I think this is a bug.
What does the standard say ?
8.5.1/2 : When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause.
It is explained that:
8.5/14 : (...) is called copy-initialization. [ Note: Copy-initialization may invoke a move (12.8). —end note ]
But I found no evidence in 12.8 that in your specific case a move would be required.
8.5.4/3 Otherwise, if T is a class type, constructors are considered. If T has an initializer-list constructor, the argument list consists of the initializer list as a single argument; otherwise, the argument list consists of the elements of the initializer list. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3).
So in principle you code should work !
Is it a bug ? Trying the experimental way
I commented out the delete of the move constructor, to benefit from the implicit move constructor. Strangely, I then got the following error message:
Compilation error time: 0 memory: 3232 signal:0
prog.cpp: In constructor 'Aggr::Aggr()':
prog.cpp:19:28: error: use of deleted function 'A::A(const A&)'
Aggr() : arr{{"a"}, {"b"}} {}
^
prog.cpp:10:3: note: declared here
A(const A&) = delete
So now he complains about a missing copy constructor !
Even more strangely, I then provided my own move constructor instead of the implicit one : here it compiled the code successfully !!
Finally I provided both a copy and a move and added some tracing:
class A {
private:
std::string s;
public:
A() = delete;
A(const A&) { std::cout<<"copy\n";} //= delete;
A(A&&) { std::cout<<"move\n";} //= delete;
A(const std::string &a) : s(a) { std::cout<<"string ctor\n";}
};
And when I create an Aggr
object, it just displays:
string ctor
string ctor
showing that the array member is initialized form the string constructor using copy elision as we would have expected.
All these tests were performed with gcc-9.4.2 on ideone with C++14 option.
Conclusion
The fact that the same code fails to compile with implicit move ctor and succeeds with a user-defined move ctor looks very seriously like a bug.
The fact that the move constructor is not used when it's available reinforces this impression.
Consequently, I've reported this bug.
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