2996. Missing rvalue overloads for shared_ptr operations

Section: 23.11.3 [util.smartptr.shared], 23.11.3.9 [util.smartptr.shared.cast] Status: LEWG Submitter: Geoffrey Romer Opened: 2017-07-07 Last modified: 2017-07-12

Priority: Not Prioritized

View other active issues in [util.smartptr.shared].

View all other issues in [util.smartptr.shared].

View all issues with LEWG status.

Discussion:

The shared_ptr aliasing constructor and the shared_ptr casts are specified to take a shared_ptr by const reference and construct a new shared_ptr that shares ownership with it, and yet they have no corresponding rvalue reference overloads. That results in an unnecessary refcount increment/decrement when those operations are given an rvalue. Rvalue overloads can't be added as a conforming extension because they observably change semantics (but mostly only for code that does unreasonable things like pass an argument by move and then rely on the fact that it's unchanged), and [res.on.arguments]/p1.3 doesn't help because it only applies to rvalue reference parameters.

This issue is related to P0390R0.

[2017-07 Toronto Tuesday PM issue prioritization]

Status LEWG

Proposed resolution:

This wording is relative to N4659.

  1. Edit 23.10.2 [memory.syn], header <memory> synopsis, as indicated:

    [Drafting note: The text with light-blue background shows a currently missing declaration of reinterpret_pointer_cast in the header <memory> synopsis. The project editor is kindly asked to consider adding it for consistency. — end drafting note]

    […]
    // 23.11.3.9 [util.smartptr.shared.cast], shared_ptr casts
    template<class T, class U>
    shared_ptr<T> static_pointer_cast(const shared_ptr<U>& r) noexcept;
    template<class T, class U>
    shared_ptr<T> static_pointer_cast(shared_ptr<U>&& r) noexcept;
    template<class T, class U>
    shared_ptr<T> dynamic_pointer_cast(const shared_ptr<U>& r) noexcept;
    template<class T, class U>
    shared_ptr<T> dynamic_pointer_cast(shared_ptr<U>&& r) noexcept;
    template<class T, class U>
    shared_ptr<T> const_pointer_cast(const shared_ptr<U>& r) noexcept;
    template<class T, class U>
    shared_ptr<T> const_pointer_cast(shared_ptr<U>&& r) noexcept;
    template<class T, class U>
    shared_ptr<T> reinterpret_pointer_cast(const shared_ptr<U>& r) noexcept;
    template<class T, class U>
    shared_ptr<T> reinterpret_pointer_cast(shared_ptr<U>&& r) noexcept;
    […]
    
  2. Edit 23.11.3 [util.smartptr.shared], class template shared_ptr synopsis, as indicated:

    template<class T> class shared_ptr {
    public:
      […]
      // 23.11.3.1 [util.smartptr.shared.const], constructors
      […]
      template <class D, class A> shared_ptr(nullptr_t p, D d, A a);
      template<class Y> shared_ptr(const shared_ptr<Y>& r, element_type* p) noexcept;
      template<class Y> shared_ptr(shared_ptr<Y>&& r, element_type* p) noexcept;
      shared_ptr(const shared_ptr& r) noexcept;
      […]
    };
    
    […]
    // 23.11.3.9 [util.smartptr.shared.cast], shared_ptr casts
    template<class T, class U>
    shared_ptr<T> static_pointer_cast(const shared_ptr<U>& r) noexcept;
    template<class T, class U>
    shared_ptr<T> static_pointer_cast(shared_ptr<U>&& r) noexcept;
    template<class T, class U>
    shared_ptr<T> dynamic_pointer_cast(const shared_ptr<U>& r) noexcept;
    template<class T, class U>
    shared_ptr<T> dynamic_pointer_cast(shared_ptr<U>&& r) noexcept;
    template<class T, class U>
    shared_ptr<T> const_pointer_cast(const shared_ptr<U>& r) noexcept;
    template<class T, class U>
    shared_ptr<T> const_pointer_cast(shared_ptr<U>&& r) noexcept;
    template<class T, class U>
    shared_ptr<T> reinterpret_pointer_cast(const shared_ptr<U>& r) noexcept;
    template<class T, class U>
    shared_ptr<T> reinterpret_pointer_cast(shared_ptr<U>&& r) noexcept;
    […]
    
  3. Edit 23.11.3.1 [util.smartptr.shared.const] as indicated:

    [Drafting note: the use_count() postcondition can safely be deleted because it is redundant with the "shares ownership" wording in the Effects. — end drafting note]

    template<class Y> shared_ptr(const shared_ptr<Y>& r, element_type* p) noexcept;
    template<class Y> shared_ptr(shared_ptr<Y>&& r, element_type* p) noexcept;
    

    -14- Effects: Constructs a shared_ptr instance that stores p and shares ownership with the initial value of r.

    -15- Postconditions: get() == p && use_count() == r.use_count(). For the second overload, r is empty and r.get() == nullptr.

    -16- [Note: To avoid the possibility of a dangling pointer, the user of this constructor must ensure that p remains valid at least until the ownership group of r is destroyed. — end note]

    -17- [Note: This constructor allows creation of an empty shared_ptr instance with a non-null stored pointer. — end note]

  4. Edit 23.11.3.9 [util.smartptr.shared.cast] as indicated:

    template<class T, class U>
      shared_ptr<T> static_pointer_cast(const shared_ptr<U>& r) noexcept;
    template<class T, class U>
      shared_ptr<T> static_pointer_cast(shared_ptr<U>&& r) noexcept;
    

    -1- Requires: The expression static_cast<T*>((U*)0) shall be well formed.

    -2- Returns: shared_ptr<T>(rR, static_cast<typename shared_ptr<T>::element_type*>(r.get())), where R is r for the first overload, and std::move(r) for the second.

    -3- [Note: The seemingly equivalent expression shared_ptr<T>(static_cast<T*>(r.get())) will eventually result in undefined behavior, attempting to delete the same object twice. — end note]

    template<class T, class U>
      shared_ptr<T> dynamic_pointer_cast(const shared_ptr<U>& r) noexcept;
    template<class T, class U>
      shared_ptr<T> dynamic_pointer_cast(shared_ptr<U>&& r) noexcept;
    

    -4- Requires: The expression dynamic_cast<T*>((U*)0) shall be well formed and shall have well defined behavior.

    -5- Returns:

    1. (5.1) — When dynamic_cast<typename shared_ptr<T>::element_type*>(r.get()) returns a nonzero value p, shared_ptr<T>(rR, p), where R is r for the first overload, and std::move(r) for the second.

    2. (5.2) — Otherwise, shared_ptr<T>().

    -6- [Note: The seemingly equivalent expression shared_ptr<T>(dynamic_cast<T*>(r.get())) will eventually result in undefined behavior, attempting to delete the same object twice. — end note]

    template<class T, class U>
      shared_ptr<T> const_pointer_cast(const shared_ptr<U>& r) noexcept;
    template<class T, class U>
      shared_ptr<T> const_pointer_cast(shared_ptr<U>&& r) noexcept;
    

    -7- Requires: The expression const_cast<T*>((U*)0) shall be well formed.

    -8- Returns: shared_ptr<T>(rR, const_cast<typename shared_ptr<T>::element_type*>(r.get())), where R is r for the first overload, and std::move(r) for the second.

    -9- [Note: The seemingly equivalent expression shared_ptr<T>(const_cast<T*>(r.get())) will eventually result in undefined behavior, attempting to delete the same object twice. — end note]

    template<class T, class U>
      shared_ptr<T> reinterpret_pointer_cast(const shared_ptr<U>& r) noexcept;
    template<class T, class U>
      shared_ptr<T> reinterpret_pointer_cast(shared_ptr<U>&& r) noexcept;
    

    -10- Requires: The expression reinterpret_cast<T*>((U*)0) shall be well formed.

    -11- Returns: shared_ptr<T>(rR, reinterpret_cast<typename shared_ptr<T>::element_type*>(r.get())), where R is r for the first overload, and std::move(r) for the second.

    -12- [Note: The seemingly equivalent expression shared_ptr<T>(reinterpret_cast<T*>(r.get())) will eventually result in undefined behavior, attempting to delete the same object twice. — end note]