How to initialize array of classes with deleted copy constructor (C++11)
Asked Answered
B

2

13

The existing question on Why can't I initialise an array of objects if they have private copy constructors? specifically refers to C++03. I know from that question that what I am trying to do is not allowed in C++03 but I thought that it should be possible in C++11

I have a non-movable class (call it Child) and I need to initialize an array of Child in the constructor of another class (call it Parent). By "non-movable" I mean that the address of a Child object has to remain the same during that object's lifetime. What is the correct way to do this?

With C++11 I've tried the following:

class Child
{
public:
    Child (int x) {}
    ~Child () {}

    Child (const Child &) = delete;
};

class Parent
{
public:
    Parent () : children {{5}, {7}} {}

private:
    Child children[2];
};

This code compiles fine with Clang 3.5.0, but GCC 4.9.1 complains that I am trying to use the deleted copy constructor:

test.cc: In constructor ‘Parent::Parent()’:
test.cc:13:35: error: use of deleted function ‘Child::Child(const Child&)’
     Parent () : children {{5}, {7}} {}
                                   ^
test.cc:7:5: note: declared here
     Child (const Child &) = delete;
     ^

I've read about the difference between copy-initialization and direct-initialization (here and here, for example), and I want to avoid calling the copy constructor by using direct-initialization. Am I getting the syntax wrong? Is this a bug in GCC? Or is what I am trying to do just not possible?

Broider answered 1/11, 2014 at 1:48 Comment(7)
Seems to me this is a clang bug, and not a gcc bug. clang fails to compile the code if you change it to children {Child{5}, Child{7}}, which should behave identically to what you've posted. A workaround would be to use a vector and emplace the Child objects.Stralsund
g++ succeeds with Child children[2] { {5}, {7} }; which should be identical to the version where the same initializer occurs in the ctor initializer list; both are covered by [dcl.init.list]/3Agree
Reading through the sections on initialization this code seems correct; children[2] = { {5}, {7} } says that children[0] is copy-initializaed from {5}, i.e. it's the same as Child c = { 5 };, and that is covered by [dcl.init.list] again which invokes the constructor for c that takes int (without involving a copy).Agree
@Praetorian: children {Child {5}, Child {7}} is different, I think. Putting Child {5} inside the braces forces the compiler to (nominally) create a temporary object which is then copied to initialize the array.Broider
@MattMcNabb: Strange to me is that if I comment out the destructor in Child, then GCC accepts the code. I believe the absence of a (non-default) destructor means that Child is then a literal type. Do you know if there are different initialization rules for literal types?Broider
@JohnLindgren Stack Overflow flagged this answer as related ... I initially flagged it (StackOverflow doesn't do all that much on its own) but given the comments I retracted my vote, in part because emplacement offers an answer fitting your specific phrased requirements...and also because it seems there's some nuances here worth looking at in the compilers...even if it's finding a bug.Silence
Created gcc.gnu.org/bugzilla/show_bug.cgi?id=63707. We'll see what the GCC developers have to say.Broider
P
4

I agree with the comments that this seems to be a GCC bug (reported as 63707).

It only fails to compile when the type in the array has a user-defined destructor, which doesn't make sense to me.

Petrol answered 28/1, 2015 at 11:52 Comment(2)
Sometimes it also fails without destructor: #31906983Depoliti
@peku33, yes, I gave a similar example in the GCC bug report. The problem happens with non-trivial destructors, not only user-defined ones.Petrol
T
-1

I came through a similar issue, namely that this code

#include <iostream>

class Widget {
public:
    Widget(int i) { std::cout << "Ctor " << i << std::endl; }

    Widget(const Widget&); // = delete;
};

int main() {
    Widget w = 123;
}

compiled and gave the expected result, but after uncommenting the = delete it failed to compile with gcc-4.9.

After reading the standard I believe the answer lies in the second item of highest indentation in 8.5/16, which is cited below.

What basically seems to happen is that the compiler conceptually wants to create a temporary of type Widget and direct-initialize the actual object w from that temporary through the copy constructor. Since the copy constructor is deleted, compilation stops. If the copy constructor was not deleted, the compiler would later realize that it may elide the copy, but it does not get that far.

Here is the relevant part:

[...] for the [...] copy-initialization cases [...] user-defined conversion sequences that can convert from the source type to the destination type [...] are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3). [...] The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the cv-unqualified version of the destination type. [...] The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see 12.2, 12.8.

But I may be wrong since there is a lot in the [...] parts that I did not understand.

Tawnytawnya answered 17/11, 2014 at 0:43 Comment(5)
I don't think this is really related. Widget w = 123; uses the copy constructor, Widget w{123}; is direct initialization.Janycejanyte
This is definitely a different situation. Creating a Widget on the stack requires an accessible destructor, because it gets called automatically for stack objects. If you have a deleted destructor you can't create it on the stack. That's nothing to do with the question.Petrol
@JonathanWakely It is not the destructor that is deleted in my example.Tawnytawnya
You're right, sorry. Still a different situation though. Widget w = 123; means Widget w = Widget(123) which requires a copy constructor. That's a well-known feature of C++, nothing to do with C++11, and unrelated to this question.Petrol
@JonathanWakely I understood the quoted part of the standard (at the time when I wrote this) to also talk about the situation in the OP. I am suggesting that the failing line is equivalent to Parent () : children { Child{5}, Child{7}} {} and then the actual array elements are initialized from the temporaries. But maybe I got something wrong.Tawnytawnya

© 2022 - 2024 — McMap. All rights reserved.