17

Let's say I have a class with a member array of std::atomics, where the array is sized via a computation (i.e. it may change based on other constants elsewhere in the program):

class Foo {
  static constexpr size_t kArraySize = ComputeArraySize();
  std::atomic<size_t> atomics_[kArraySize];
};

What is the most elegant way to ensure that the atomics are all initialized to zero? Can I do better than looping over the array in Foo's constructor and explicitly storing zero? Does the answer differ for std::array?

Normally I would use a brace initializer here, but the derived length (which may be long) makes it difficult.

Note that I cannot assume that the instance of Foo has static storage duration.

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
jacobsa
  • 5,719
  • 1
  • 28
  • 60
  • declare as `std::atomic atomics_[kArraySize] = {};` ? I'm not sure what the rules are for atomic initialization – Ryan Haining Jun 19 '14 at 03:34
  • ... Is to do nothing. Everything static is zero-initialized at program startup. – n. m. could be an AI Jun 19 '14 at 05:12
  • @Ryan Haining: Yep, that does work for a plain C style array. Doesn't seem to work for `std::array, N>` though, even though the standard seems to say it should. @n.m.: "Note that I cannot assume that the instance of Foo has static storage duration". – jacobsa Jun 19 '14 at 05:16
  • "Doesn't seem to work for std::array, N>". Works for me. Which compiler version are you using? – n. m. could be an AI Jun 19 '14 at 06:25
  • I can vouch for that. It does work as expected with gcc 4.8.2 and clang 3.4.1. – shakurov Jun 19 '14 at 06:28
  • Thanks, all. I can't seem to reproduce the issue with a small program, only one with multiple levels of template instantiation. This is a Google-internal version of GCC and Clang, so it's possible it's weird somehow; will file an internal bug. If one of you submits an answer, I'll be happy to mark it accepted. – jacobsa Jun 19 '14 at 10:01
  • @jacobsa as you said, it supports aggregate initialization but I wasn't clear on what it does with the omitted elements – Ryan Haining Jun 19 '14 at 17:22

1 Answers1

13

Okay, I believe I've worked this through. Both of these will initialize all of the atomics to zero:

std::atomic<size_t> plain_array[kArraySize] = {};
std::array<std::atomic<size_t>, kArraySize> std_array = {};

Here's the logic:

  • [dcl.init.aggr]/1 defines arrays to be aggregates.

  • [array.cons]/1 mandates that std::array also be an aggregate.

  • [dcl.init.aggr]/7 says that if there are fewer elements of the initializer list than there are members in the aggregate, then the remaining members shall be initialized from an empty initializer list. In this case, that's all members.

  • [dcl.init.list]/3 defines list-initialization from an empty list for a class with a default constructor (as with std::atomic) to cause value-initialization.

  • [dcl.init]/7 says that classes without user-provided constructors are zero-initialized. Assuming that std::array<T> contains an array of T, and that the zero representation of std::atomic<size_t> is what we expect, then we're good.

Now, std::atomic does have a user-provided constructor, just not a user-provided default constructor (the latter is explicitly defaulted). So it doesn't technically fit the conditions of the last point. But it seems this is an error in the standard, and has been fixed in more recent drafts.

jacobsa
  • 5,719
  • 1
  • 28
  • 60
  • 3
    I believe the underlying atomic objects are left in an uninitialized state due to the surprising behavior described in http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0883r0.pdf. Default initialization does not do what you would expect here. – Joe Jan 17 '19 at 03:41
  • 2
    @Joe Seems to have been fixed http://www.wg21.link/p0883 – dyp Jul 17 '20 at 14:56