2

There seems no way to initialize atomic members in aggregate using C++14. The following doesn't work (live on gcc 8.0.1):

#include <atomic>
#include <iostream>

struct stru {
  std::atomic_int32_t val_0;
  std::atomic_int32_t val_1;
};

int main() {
  auto p = new stru{0, 1};
  std::cout << p->val_0 << ", " << p->val_1 << std::endl; 
}

The error message:

error: use of deleted function 'std::atomic<int>::atomic(const std::atomic<int>&)'
   auto p = new stru{0, 1};
                     ^

This is because atomic types are neither copyable nor movable, and is thus not copy-initializable. The following seems to work however (live on gcc 8.0.1).

#include <atomic>
#include <iostream>

struct stru {
  std::atomic_int32_t val_0;
  std::atomic_int32_t val_1;
};

int main() {
  auto p = new stru{};
  std::cout << p->val_0 << ", " << p->val_1 << std::endl; 
}

This effectively performs zero initialization, and is thus unable to initialize to values other than zero. Is there any way to initialize to other specified values?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Lingxi
  • 14,579
  • 2
  • 37
  • 93
  • Don't post links to your compiler spew, post the spew. – jwdonahue Feb 17 '18 at 03:26
  • Your first code block compiles without warnings/errors on VS2017 and runs as expected. Am I missing something? – jwdonahue Feb 17 '18 at 03:33
  • @jwdonahue If different compilers disagree, then one got to be correct while the other be wrong. VS is known to be non-standard-conforming. BTW, what do you mean by *post the spew*? – Lingxi Feb 17 '18 at 03:38
  • He means **Put the error message in the question.** – Ben Voigt Feb 17 '18 at 03:41
  • @BenVoigt Thanks, updated. – Lingxi Feb 17 '18 at 03:43
  • @jwdonahue And there is no way to specify C++14 in VS. It always tries to follow the latest standard. – Lingxi Feb 17 '18 at 03:44
  • 4
    Have you tried the VS option [/std:c++14](https://learn.microsoft.com/sv-se/cpp/build/reference/std-specify-language-standard-version) to specify the language version you want? :-) – Bo Persson Feb 17 '18 at 04:06
  • @BoPersson Wow O_O Today I learned. – Lingxi Feb 17 '18 at 04:07
  • Apologies to all. I normally would have put some time into a standards check, compiler options, etc, and posted an answer, but I was being pulled away from my hobby by more pressing matters. – jwdonahue Feb 17 '18 at 21:28
  • @HDJEMAI: The same thing happens in clang; the C++ tag is more appropriate than [tag:gcc8]; it's not specific to gcc8. Rolled back your tag edit. – Peter Cordes May 02 '18 at 03:07

2 Answers2

2

Patrick's solution works, but his explanation doesn't look right to me. So I post the explanation I find here. For the code auto p = new stru{{0}, {1}}; aggregate initialization kicks in with the effect:

If the initializer clause is a nested braced-init-list (which is not an expression), the corresponding array element/class member/public base (since C++17) is list-initialized from that clause: aggregate initialization is recursive.

As a result, instead of copy initialization, the members are copy list-initialized from the braced-init-list.

Lingxi
  • 14,579
  • 2
  • 37
  • 93
  • Related: [Copy constructor for classes with atomic member](https://stackoverflow.com/questions/19961043/copy-constructor-for-classes-with-atomic-member/46045691#46045691): if you are writing a custom constructor (regular constructor or (probably a bad idea) copy-constructor): definitely pass args to the `std::atomic` constructor with `:atomic_member(a)` syntax, rather than using an atomic store (assignment) inside the constructor after letting an atomic member be default-initialized. – Peter Cordes Feb 17 '18 at 07:35
  • @PeterCordes Indeed. However, I'm somewhat concerned with `atomic_flag`. It seems that only the `std::atomic_flag v = ATOMIC_FLAG_INIT;` syntax is guaranteed to work (see [here](http://en.cppreference.com/w/cpp/atomic/ATOMIC_FLAG_INIT)). So, I'm wondering whether `:atomic_member(a)` is OK, or do I have to use in-class default member initializer. – Lingxi Feb 17 '18 at 08:26
  • I'm talking about the `std::atomic` template; `std::atomic_flag` is allowed by the standard to be different, I think because it's the only one that's guaranteed to be lock-free. `:atomic_member(a)` is definitely ok (and is lock-free for `int` / `long` and other small `T` types on typical C++ implementations). `:atomic_flag_member(a)` isn't guaranteed to even compile, because `std::atomic_flag` doesn't have a value constructor. (It might compile on implementations where it doesn't need to be special and is defined on top of `std::atomic`; the standard *allows* it to be special.) – Peter Cordes Feb 17 '18 at 08:34
  • When you say "the members are copy initialized from the braced-init-list" -- which constructor is picked in that case? – Patrick Collins Feb 19 '18 at 21:18
  • @PatrickCollins It means if the selected constructor is declared `explicit`, the program is ill-formed. For example, `unique_ptr p = {new int()}` won't compile. – Lingxi Feb 20 '18 at 02:41
-1

1 and 0 aren't std::atomic_int32_ts. The following works:

#include <atomic>
#include <iostream>

struct stru {
  std::atomic_int32_t val_0;
  std::atomic_int32_t val_1;
};

int main() {
  auto p = new stru{{0}, {1}};
  std::cout << p->val_0 << ", " << p->val_1 << std::endl; 
}

EDIT: Why does this happen? Copy initialization kicks in for each element in the initializer list:

Each direct public...non-static class member, in order of array subscript/appearance in the class definition, is copy-initialized from the corresponding clause of the initializer list.

In your example, 1 and 0 are implicitly converted to std::atomic_int32_t, and a std::atomic_int32_t can't be copy-initialized from a std::atomic_int32_t. In the extra {} version, val_0 and val_1 are copy initialized from std::initializer_list<int>s, which is fine.

Patrick Collins
  • 10,306
  • 5
  • 30
  • 69