Initialization of member array objects avoiding move constructor

915 views Asked by At

I'm trying to create and initialize a class that contains a member array of a non-trivial class, which contains some state and (around some corners) std::atomic_flag. As of C++11 one should be able to initialize member arrays.

The code (stripped down to minimum) looks like this:

class spinlock
{
    std::atomic_flag flag;
    bool try_lock() { return !flag.test_and_set(std::memory_order_acquire); }
public:
    spinlock() : flag(ATOMIC_FLAG_INIT){};
    void lock()     { while(!try_lock()) ; }
    void unlock()   { flag.clear(std::memory_order_release); }
};

class foo
{
    spinlock lock;
    unsigned int state;
public:
    foo(unsigned int in) : state(in) {}
};

class bar
{
    foo x[4] = {1,2,3,4}; // want each foo to have different state
public:
    //...
};

If I understand the compiler output correctly, this seems not to construct the member array, but to construct temporaries and invoke the move/copy constructor, which subsequently calls move constructors in sub-classes, and that one happens to be deleted in std::atomic_flag. The compiler output that I get (gcc 4.8.1) is:

[...] error: use of deleted function 'foo::foo(foo&&)'
note: 'foo::foo(foo&&)' is implicitly deleted because the default definition would be ill-formed
error: use of deleted function 'spinlock::spinlock(spinlock&&)'
note: 'spinlock::spinlock(spinlock&&)' is implicitly deleted because [...]
error: use of deleted function 'std::atomic_flag::atomic_flag(const std::atomic_flag&)'
In file included from [...]/i686-w64-mingw32/4.8.1/include/c++/atomic:41:0
[etc]

If I remove the array and instead just put a single foo member inside bar, I can properly initialize it using standard constructor initializers, or using the new in-declaration initialization, no problem whatsoever. Doing the same thing with a member array fails with the above error, no matter what I try.

I don't really mind that array elements are apparently constructed as temporaries and then moved rather than directly constructed, but the fact that it doesn't compile is obviously somewhat of a showstopper.

Is there a way I either force the compiler to construct (not move) the array elements, or a way I can work around this?

2

There are 2 answers

3
dyp On BEST ANSWER

Here's a minimal example exposing the problem:

struct noncopyable
{
    noncopyable(int) {};
    noncopyable(noncopyable const&) = delete;
};

int main()
{
    noncopyable f0 = {1};
    noncopyable f1 = 1;
}

Although the two initializations of f0 and f1 have the same form (are both copy-initialization), f0 uses list-initialization which directly calls a constructor, whereas the initialization of f1 is essentially equivalent to foo f1 = foo(1); (create a temporary and copy it to f1).

This slight difference also manifests in the array case:

noncopyable f0[] = {{1}, {2}, {3}, {4}};
noncopyable f1[] = {1, 2, 3, 4};

Aggregate-initialization is defined as copy-initialization of the members [dcl.init.aggr]/2

Each member is copy-initialized from the corresponding initializer-clause.

Therefore, f1 essentially says f1[0] = 1, f1[1] = 2, .. (this notation shall describe the initializations of the array elements), which has the same problem as above. OTOH, f0[0] = {1} (as an initialization) uses list-initialization again, which directly calls the constructor and does not (semantically) create a temporary.


You could make your converting constructors explicit ;) this could avoid some confusion.

Edit: Won't work, copy-initialization from a braced-init-list may not use an explicit constructor. That is, for

struct expl
{
    explicit expl(int) {};
};

the initialization expl e = {1}; is ill-formed. For the same reason, expl e[] = {{1}}; is ill-formed. expl e {1}; is well-formed, still.

0
Ярик Зюлин On

Gcc refuses to compile list-initialization of arrays of objects with virtual destructor up to version 10.2. In 10.3 that was fixed. E.g. if noncopyable from @dyp answer had a virtual destructor, gcc fails to compile line:

noncopyable f0[] = {{1}, {2}, {3}, {4}}; 

arguing to deleted copy and move c-rs. But successfully compiles under 10.3 and higher.