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

Section: 32.6.1 [atomics.types.operations] Status: SG1 Submitter: Daniel Krügler Opened: 2013-10-03 Last modified: 2017-07-14

Priority: Not Prioritized

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

View all issues with SG1 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 32.6 [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 stateThe value of the atomic object is default-initialized (11.6 [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 stateDefault-initializes (11.6 [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-iInitializes 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 32.8 [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 thepointed 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.

Proposed resolution:

This wording is relative to N4659.

Modify 32.6.1 [atomics.types.operations] as indicated:

-?- Initialization of an atomic object is not an atomic operation (6.8.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 (11.6 [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.8.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]