3

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?

Damon
  • 67,688
  • 20
  • 135
  • 185
  • The problem does not seem to be related to member arrays, but to aggregate initialization, [dcl.init.aggr]/2 "Each member is copy-initialized from the corresponding *initializer-clause*." – dyp Dec 16 '13 at 19:54
  • Seems you are right, the same error is generated if I create and initialize an array of `foo`s inside `main`. So if this is bound to aggregate initialization, my only choice would be to add a default constructor to `foo` and create an array of default objects, and then later change state manually? – Damon Dec 16 '13 at 19:58
  • Use more braces: `foo x[4] = {{1},{2},{3},{4}};` I might need a while to get my eyebrows down before I can explain why that works.. – dyp Dec 16 '13 at 19:58
  • That works! Care to make this an answer? :) – Damon Dec 16 '13 at 19:59
  • Isn't [tag:member-array] a bit ... specific to have its own tag? – Bernhard Barker Dec 17 '13 at 14:23
  • @Dukeling: Maybe, but I wouldn't know how to tagd it any better (feel free to retag if you like). Is there any harm in having that tag? As far as I remember, tags go away after some time when not enough people use them, no? – Damon Dec 17 '13 at 15:27
  • I'm not really sure that it's not useful as a tag to remove it. If you can imagine a dozen question specific to member arrays, then it's probably fine. It only goes away once there's no questions left with the tag. If I were to have retagged it, I probably would've used [tag:member-variables]. – Bernhard Barker Dec 17 '13 at 16:40
  • @Dukeling: Done that. – Damon Dec 18 '13 at 10:25

2 Answers2

3

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.

dyp
  • 38,334
  • 13
  • 112
  • 177
  • _"could make converting constructors explicit"_ -- interestingly, that was among the things I tried while being clueless why it didn't work. That only gave a different "converting to [...] from initializer list would use explicit constructor"error. The explanation about list-initialization was very conclusive (funny how totally incomprehensible things suddenly appear so obvious when someone explains them). Thank you very much :) – Damon Dec 16 '13 at 20:19
  • @Damon I now think `explicit` cannot help; this probably leads to the question if there *is* any way to initialize an array of noncopyable, nonmovable types with explicit ctors. Also see [this question](http://stackoverflow.com/q/15962491/420683) – dyp Dec 17 '13 at 15:53
  • That is a very interesting question indeed. Actually you would think there _must_ be a way, since none of these properties is particularly "unusual" or "forbidden", and there seems to be no good reason not to allow it. On the contrary, it seems seriously weird that apparently there are entirely legal constructs that you cannot use because you cannot create them with a well-defined state (you _could_ set the state after default-constructing to default state, but meh). – Damon Dec 17 '13 at 16:08
1

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.