3195. What is the stored pointer value of an empty weak_ptr?

Section: 20.3.2.3.2 [util.smartptr.weak.const] Status: C++23 Submitter: Casey Carter Opened: 2019-03-15 Last modified: 2023-11-22

Priority: 2

View all issues with C++23 status.

Discussion:

20.3.2.3.2 [util.smartptr.weak.const] specifies weak_ptr's default constructor:

constexpr weak_ptr() noexcept;

1 Effects: Constructs an empty weak_ptr object.

2 Ensures: use_count() == 0.

and shared_ptr converting constructor template:

weak_ptr(const weak_ptr& r) noexcept;
template<class Y> weak_ptr(const weak_ptr<Y>& r) noexcept;
template<class Y> weak_ptr(const shared_ptr<Y>& r) noexcept;

3 Remarks: The second and third constructors shall not participate in overload resolution unless Y* is compatible with T*.

4 Effects: If r is empty, constructs an empty weak_ptr object; otherwise, constructs a weak_ptr object that shares ownership with r and stores a copy of the pointer stored in r.

5 Ensures: use_count() == r.use_count().

Note that neither specifies the value of the stored pointer when the resulting weak_ptr is empty. This didn't matter — the stored pointer value was unobservable for an empty weak_ptr — until we added atomic<weak_ptr>. 32.5.8.7.3 [util.smartptr.atomic.weak]/15 says:

Remarks: Two weak_ptr objects are equivalent if they store the same pointer value and either share ownership, or both are empty. The weak form may fail spuriously. See 32.5.8.2 [atomics.types.operations].

Two empty weak_ptr objects that store different pointer values are not equivalent. We could correct this by changing 32.5.8.7.3 [util.smartptr.atomic.weak]/15 to "Two weak_ptr objects are equivalent if they are both empty, or if they share ownership and store the same pointer value." In practice, an implementation of atomic<weak_ptr> will CAS on both the ownership (control block pointer) and stored pointer value, so it seems cleaner to pin down the stored pointer value of an empty weak_ptr.

[2019-06-09 Priority set to 2 after reflector discussion]

Previous resolution [SUPERSEDED]

This wording is relative to N4810.

  1. Modify 20.3.2.3.2 [util.smartptr.weak.const] as indicated (note the drive-by edit to cleanup the occurrences of "constructs an object of class foo"):

    constexpr weak_ptr() noexcept;
    

    -1- Effects: Constructs an empty weak_ptr object that stores a null pointer value.

    -2- Ensures: use_count() == 0.

    weak_ptr(const weak_ptr& r) noexcept;
    template<class Y> weak_ptr(const weak_ptr<Y>& r) noexcept;
    template<class Y> weak_ptr(const shared_ptr<Y>& r) noexcept;
    

    -3- Remarks: The second and third constructors shall not participate in overload resolution unless Y* is compatible with T*.

    -4- Effects: If r is empty, constructs an empty weak_ptr object that stores a null pointer value; otherwise, constructs a weak_ptr object that shares ownership with r and stores a copy of the pointer stored in r.

    -5- Ensures: use_count() == r.use_count().

[2020-02-14 Casey updates P/R per LWG instruction]

While reviewing the P/R in Prague, Tim Song noticed that the stored pointer value of a moved-from weak_ptr must also be specified.

[2020-02-16; Prague]

Reviewed revised wording and moved to Ready for Varna.

[2020-11-09 Approved In November virtual meeting. Status changed: Ready → WP.]

Proposed resolution:

This wording is relative to N4849.

  1. Modify 20.3.2.3.2 [util.smartptr.weak.const] as indicated:

    constexpr weak_ptr() noexcept;
    

    -1- Effects: Constructs an empty weak_ptr object that stores a null pointer value.

    -2- Postconditions: use_count() == 0.

    weak_ptr(const weak_ptr& r) noexcept;
    template<class Y> weak_ptr(const weak_ptr<Y>& r) noexcept;
    template<class Y> weak_ptr(const shared_ptr<Y>& r) noexcept;
    

    -3- Remarks: The second and third constructors shall not participate in overload resolution unless Y* is compatible with T*.

    -4- Effects: If r is empty, constructs an empty weak_ptr object that stores a null pointer value; otherwise, constructs a weak_ptr object that shares ownership with r and stores a copy of the pointer stored in r.

    -5- Postconditions: use_count() == r.use_count().

    weak_ptr(weak_ptr&& r) noexcept;
    template<class Y> weak_ptr(weak_ptr<Y>&& r) noexcept;
    

    -6- Remarks: The second constructor shall not participate in overload resolution unless Y* is compatible with T*.

    -7- Effects: Move constructs a weak_ptr instance from r.

    -8- Postconditions: *this shall containcontains the old value of r. r shall beis empty., stores a null pointer value, and r.use_count() == 0.