ISO/ IEC JTC1/SC22/WG21 p0390r0

Document number:    P0390R0
Date:               2016-06-04
Audience:           Library Working Group, Library Evolution Working Group
Reply-to:           Nickolas Pokhylets <pohilets at gmail dot com>

A Proposal to Add Pointer Cast Functions with Move Semantics to the Standard Library

I. Motivation

Currently all pointer cast functions take their argument by const reference and there are no versions that would take argument by value or by rvalue reference. As a result, the following code makes a completely unnecessary atomic increment and decrement:
   
   struct Foo {};

   struct Bar : public Foo {};

   std::shared_ptr<Foo> foo();

   auto bar = std::static_pointer_cast<Bar>(foo());

Atomic increment and decrement are relatively expensive operations and in many cases can be optimized away by the programmer though careful use of moving pointer objects instead of copying them. But currently such optimization cannot be performed when any of pointer cast functions are used (std::static_pointer_cast(), std::dynamic_pointer_cast(), std::const_pointer_cast(), std::reinterpret_pointer_cast()).

II. Proposed Text

Proposed solution is to add versions that take parameter by rvalue reference [20.10.2.2.9], [util.smartptr.shared.cast]:

    template<class T, class U> std::shared_ptr<T> static_pointer_cast(const std::shared_ptr<U>& r);      // since C++11
    template<class T, class U> std::shared_ptr<T> dynamic_pointer_cast(const std::shared_ptr<U>& r);     // since C++11
    template<class T, class U> std::shared_ptr<T> const_pointer_cast(const std::shared_ptr<U>& r);       // since C++11
    template<class T, class U> std::shared_ptr<T> reinterpret_pointer_cast(const std::shared_ptr<U>& r); // since C++17

    template<class T, class U> std::shared_ptr<T> static_pointer_cast(std::shared_ptr<U>&& r);      // proposed addition
    template<class T, class U> std::shared_ptr<T> dynamic_pointer_cast(std::shared_ptr<U>&& r);     // proposed addition
    template<class T, class U> std::shared_ptr<T> const_pointer_cast(std::shared_ptr<U>&& r);       // proposed addition
    template<class T, class U> std::shared_ptr<T> reinterpret_pointer_cast(std::shared_ptr<U>&& r); // proposed addition

And a constructor needed to implement these functions [20.10.2.2.1], [util.smartptr.shared.const]:

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

III. Impact On the Standard

Proposal adds overloaded versions of the functions that already exist in the standard library. It requires modification of the existing headers, but does not require any changes in the core language.

IV. Design Decisions

Alternative to having two versions of each function is to have a single version that takes argument by value. But then in case of passing lvalue into dynamic_pointer_cast that fails, there is a redundant increment/decrement.

Semantics of dynamic_pointer_cast that takes argument by rvalue references needs explanation.

In case of successful cast, semantics is clear - argument is moved and ownership is transferred to the resulting object.

But case of failing dynamic cast may be confusing to some programmers (according to thread in [std-discussion]). Proposed semantics is to keep argument unmodified in this case. This enables doing chained dynamic casts with optimal performance.

    std::shared_ptr<T> px = ...;
    if (auto x = std::dynamic_pointer_cast<X>(std::move(px))) {
        // ...
    } else if (auto y = std::dynamic_pointer_cast<Y>(std::move(px))) {
        // ...
    } else if (auto z = std::dynamic_pointer_cast<Z>(std::move(px))) {
        // ...
    } else {
        // ...
    }

Also, this semantics follows the precedent of the std::map::try_emplace() - argument passed by rvalue-reference is moved conditionally.

Alternatives to this semantics are:
* Always move the argument regardless of cast result
* Don't add version of dynamic_pointer_cast() that takes rvalue reference at all.

Both of these alternatives limit developer's possibilities in writing optimal reference counting code, and first one may be considered equally confusing.