This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of Resolved status.

### 2334. `atomic`'s default constructor requires "uninitialized" state even for types with non-trivial default-constructor

Section: 31.8.2 [atomics.types.operations] Status: Resolved Submitter: Daniel Krügler Opened: 2013-10-03 Last modified: 2019-11-19

Priority: Not Prioritized

View other active issues in [atomics.types.operations].

View all other issues in [atomics.types.operations].

View all issues with Resolved status.

Discussion:

According to 99 [atomics.types.operations.req] p4,

>     A::A() noexcept = default;

> > Effects: leaves the atomic object in an uninitialized state. [Note: These semantics ensure compatibility with `C`. — end note]

This implementation requirement is OK for POD types, like `int`, but 31.8 [atomics.types.generic] p1 intentionally allows template arguments of `atomic` with a non-trivial default constructor ("The type of the template argument `T` shall be trivially copyable (3.9)"), so this wording can be read in a way that makes the behaviour of the following code undefined:

>     #include <atomic>
>     #include <iostream>

>     struct S {
>       S() noexcept : v(42) {}
>       int v;
>     };

>     int main() {
>       std::atomic<S> as; // Default-initialization
>       std::cout << as.load().v << std::endl; // ?
>     }

For a user-defined emulation of `atomic` the expected outcome would be defined and the program would output "42", but existing implementations differ and the result value is a "random number" for at least one implementation. This seems very surprising to me.

To realize that seemingly existing requirement, an implementation is either required to violate normal language rules internally or to perform specific bit-randomization-techniques after the normal default-initialization that called the default constructor of `S`.

According to my understanding, the non-normative note in 99 [atomics.types.operations.req] p4 is intended to refer to types that are valid `C`-types, but the example type `S` is not such a type.

To make the mental model of `atomic`'s default constructor more intuitive for user-code, I suggest to clarify the wording to have the effects of default-initialization instead. The current state seems more like an unintended effect of imprecise language used here and has some similarities to wording that was incorrectly used to specify `atomic_flag` initialization as described by LWG 2159.

[2014-05-17, Daniel comments and provides alternative wording]

The current wording was considered controversial as expressed by reflector discussions. To me, the actual problem is not newly introduced by that wording, but instead is already present in basically all paragraphs specifying semantics of atomic types, since the wording never clearly distinguishes the value of the actual atomic type A and the value of the "underlying", corresponding non-atomic type C. The revised proposed wording attempts to improve the current ambiguity of these two kinds of values.

Previous resolution from Daniel [SUPERSEDED]:

> This wording is relative to N3691.

> 1. Modify 99 [atomics.types.operations.req] p4 as indicated: [Editorial note: There is no exposition-only member in `atomic`, which makes it a bit hard to specify what actually is initialized, but the usage of the term "value" seems consistent with similar wording used to specify the effects of the atomic `load` functions]

>    >     A ::A () noexcept = default;

>    > > -4- Effects: leaves the atomic object in an uninitialized state The value of the atomic object is default-initialized (9.4 [dcl.init]). [Note: These semantics ensure compatibility with `C`. — end note]

[2015-02 Cologne]

Handed over to SG1.

[2017-07 Toronto]

SG1 reviewed the PR below:

Previous resolution [SUPERSEDED]:

> This wording is relative to N3936.

> 1. Modify 99 [atomics.types.operations.req] p2 as indicated: [Editorial note: This is a near-to editorial change not directly affecting this issue, but `atomic_address` does no longer exist and the pointed to definition is relevant in the context of this issue resolution.]

>    > -2- In the following operation definitions:

>    > - an A refers to one of the atomic types.

>    > - a C refers to its corresponding non-atomic type. The `atomic_address` atomic type corresponds to the `void*` non-atomic type.

>    > - […]

> 2. Modify 99 [atomics.types.operations.req] p4 and the following as indicated: [Editorial note: There is no exposition-only member in `atomic`, which makes it a bit hard to specify what actually is initialized, but the introductory wording of 99 [atomics.types.operations.req] p2 b2 defines: "a C refers to its corresponding non-atomic type." which helps to specify the semantics in terms of "the C value referred to by the atomic object"]

>    >     A::A() noexcept = default;

>    > > -4- Effects: leaves the atomic object in an uninitialized state Default-initializes (9.4 [dcl.init]) the C value referred to by the atomic object. [Note: These semantics ensure compatibility with `C`. — end note]

>    >     constexpr A::A(C desired) noexcept;

>    > > -5- Effects: Direct-i Initializes the C value referred to by the atomic object with the value `desired`. Initialization is not an atomic operation (1.10). […]

>    > > […]

>    >     void atomic_init(volatile A* object, C desired) noexcept;
>    >     void atomic_init(A* object, C desired) noexcept;

>    > > -8- Effects: Non-atomically initializes the C value referred to by `*object` with value `desired`. […]

>    >     void atomic_store(volatile A* object, C desired) noexcept;
>    >     […]
>    >     void A::store(C desired, memory_order order = memory_order_seq_cst) noexcept;

>    > > -9- […]

>    > > -10- Effects: Atomically replaces the C value pointed to by `object` or by `this` with the value of `desired`. […]

>    > > […]

>    >     C atomic_load(const volatile A* object) noexcept;
>    >     […]
>    >     C A::load(memory_order order = memory_order_seq_cst) const noexcept;

>    > > -13- […]

>    > > -14- […]

>    > > -15- Returns: Atomically returns the C value pointed to by `object` or by `this`.

>    > > […]

>    >     C atomic_exchange(volatile A* object, C desired) noexcept;
>    >     […]
>    >     C A::exchange(C desired, memory_order order = memory_order_seq_cst) noexcept;

>    > > -18- Effects: Atomically replaces the C value pointed to by `object` or by `this` with `desired`. […]

>    > > -19- Returns: Atomically returns the C value pointed to by `object` or by `this` immediately before the effects.

>    > > […]

>    >     C atomic_fetch_key(volatile A* object, M operand) noexcept;
>    >     […]
>    >     C A::fetch_key(M operand, memory_order order = memory_order_seq_cst) noexcept;

>    > > -28- Effects: Atomically replaces the C value pointed to by `object` or by `this` with the result of the computation applied to the C value pointed to by `object` or by `this` and the given `operand`. […]

>    > > -29- Returns: Atomically, returns the C value pointed to by `object` or by `this` immediately before the effects.

>    > > […]

> 3. Modify 31.10 [atomics.flag] p5 and the following as indicated:

>    >     bool atomic_flag_test_and_set(volatile atomic_flag* object) noexcept;
>    >     […]
>    >     bool atomic_flag::test_and_set(memory_order order = memory_order_seq_cst) noexcept;

>    > > -5- Effects: Atomically sets the bool value pointed to by `object` or by `this` to `true`. […]

>    > > -6- Returns: Atomically, returns the bool value of the pointed to by `object` or by `this` immediately before the effects.

>    >     void atomic_flag_clear(volatile atomic_flag* object) noexcept;
>    >     […]
>    >     void atomic_flag::clear(memory_order order = memory_order_seq_cst) noexcept;

>    > > -7- […]

>    > > -8- Effects: Atomically sets the bool value pointed to by `object` or by `this` to `false`. […]

SG1 also reviewed another PR from Lawrence Crowl. Lawrence's feedback was that turning atomic<T> into a container of T was a mistake, even if we allow the implementation of atomic to contain a T. SG1 agreed with Lawrence, but his PR (http://wiki.edg.com/bin/view/Wg21toronto2017/DefaultInitNonContainer) had massive merge conflicts caused by the adoption of P0558. Billy O'Neal supplied a new PR, which SG1 agreed to and which LWG looked at informally. This change also makes it clearer that initialization of an atomic is not an atomic operation in all forms, changes the C compatibility example to actually be compatible with C, and removes "initialization-compatible" which is not defined anywhere.

SG1 considered moving `ATOMIC_VAR_INIT` into Annex D, as their understanding at this time is that WG14 is considering removal of that macro. However, consensus was that moving things between clauses would require a paper, and that we should wait to remove that until WG14 actually does so.

[2019-02, Monday in Kona]

While discussing Richard's P1286 paper, we noted that this issue's resolution needs to be updated based on that discussion.

Also, the idea that `atomic() noexcept = default` is ok will not fly for implementors who store additional information inside the atomic variable.

Previous resolution [SUPERSEDED]:

> This wording is relative to N4659.

> Modify 31.8.2 [atomics.types.operations] as indicated:

> -?- Initialization of an atomic object is not an atomic operation (6.9.2 [intro.multithread]). [Note: It is possible to have an access to an atomic object A race with its construction, for example by communicating the address of the just-constructed object A via a `memory_order_relaxed` operations on a suitable atomic pointer variable, and then immediately accessing A in the recieving thread. This results in undefined behavior. — end note]

> -1- [Note: Many operations are volatile-qualified. The "volatile as device register" semantics have not changed in the standard. This qualification means that volatility is preserved when applying these operations to volatile objects. It does not mean that operations on non-volatile objects become volatile. — end note]

> >     atomic() noexcept = default;

> > > -2- Effects: Leaves the atomic object in an uninitialized state. [Note: These semantics ensure compatibility with C. — end note]Initializes the atomic object with a default-initialized (9.4 [dcl.init]) value of type `T`. [Note: The default-initialized value may not be pointer-interconvertible with the atomic object. — end note]

> >     constexpr atomic(T desired) noexcept;

> > > -3- Effects: Initializes the atomic object with the value `desired`. Initialization is not an atomic operation (6.9.2 [intro.multithread]). [Note: It is possible to have an access to an atomic object A race with its construction, for example by communicating the address of the just-constructed object A to another thread via `memory_order_relaxed` operations on a suitable atomic pointer variable, and then immediately accessing A in the receiving thread. This results in undefined behavior — end note]

> >     #define ATOMIC_VAR_INIT(value) see below{value}

> > > -4- The macro expands to a token sequence suitable for constant initialization of an atomic variable of static storage duration of a type that is initialization-compatible with value. [Note: This operation may need to initialize locks. — end note] Concurrent access to the variable being initialized, even via an atomic operation, constitutes a data race. [Note: This macro ensures compatibility with C. — end note]
> > > [Example:
> > > `atomic<int>atomic_int v = ATOMIC_VAR_INIT(5);`
> > > — end example]

[2019-11; Resolved by the adoption of P0883 in Belfast.]

Proposed resolution:

Resolved by P0883.