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

### 463. `auto_ptr` usability issues

Section: 99 [auto.ptr] Status: NAD Submitter: Rani Sharoni Opened: 2003-12-07 Last modified: 2016-01-28

Priority: Not Prioritized

View all other issues in [auto.ptr].

View all issues with NAD status.

Discussion:

TC1 CWG DR #84 effectively made the `template<class Y> operator auto_ptr<Y>()` member of `auto_ptr` (20.4.5.3/4) obsolete.

The sole purpose of this obsolete conversion member is to enable copy initialization base from r-value derived (or any convertible types like cv-types) case:

    #include <memory>
    using std::auto_ptr;

    struct B {};
    struct D : B {};

    auto_ptr<D> source();
    int sink(auto_ptr<B>);
    int x1 = sink( source() ); // #1 EDG - no suitable copy constructor

The excellent analysis of conversion operations that was given in the final `auto_ptr` proposal (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/1997/N1128.pdf) explicitly specifies this case analysis (case 4). DR #84 makes the analysis wrong and actually comes to forbid the loophole that was exploited by the `auto_ptr` designers.

I didn't encounter any compliant compiler (e.g. EDG, GCC, BCC and VC) that ever allowed this case. This is probably because it requires 3 user defined conversions and in fact current compilers conform to DR #84.

I was surprised to discover that the obsolete conversion member actually has negative impact of the copy initialization base from l-value derived case:

    auto_ptr<D> dp;
    int x2 = sink(dp); // #2 EDG - more than one user-defined conversion applies

I'm sure that the original intention was allowing this initialization using the `template<class Y> auto_ptr(auto_ptr<Y>& a)` constructor (20.4.5.1/4) but since in this copy initialization it's merely user defined conversion (UDC) and the obsolete conversion member is UDC with the same rank (for the early overloading stage) there is an ambiguity between them.

Removing the obsolete member will have impact on code that explicitly invokes it:

    int y = sink(source().operator auto_ptr<B>());

IMHO no one ever wrote such awkward code and the reasonable workaround for #1 is:

    int y = sink( auto_ptr<B>(source()) );

I was even more surprised to find out that after removing the obsolete conversion member the initialization was still ill-formed: `int x3 = sink(dp); // #3 EDG - no suitable copy constructor`

This copy initialization semantically requires copy constructor which means that both template conversion constructor and the `auto_ptr_ref` conversion member (20.4.5.3/3) are required which is what was explicitly forbidden in DR #84. This is a bit amusing case in which removing ambiguity results with no candidates.

I also found exception safety issue with `auto_ptr` related to `auto_ptr_ref`:

    int f(auto_ptr<B>, std::string);
    auto_ptr<B> source2();

    // string constructor throws while auto_ptr_ref
    // "holds" the pointer
    int x4 = f(source2(), "xyz"); // #4

The theoretic execution sequence that will cause a leak:

1. call auto_ptr<B>::operator auto_ptr_ref<B>()
2. call string::string(char const*) and throw

According to 20.4.5.3/3 and 20.4.5/2 the `auto_ptr_ref` conversion member returns `auto_ptr_ref<Y>` that holds `*this` and this is another defect since the type of `*this` is `auto_ptr<X>` where `X` might be different from `Y`. Several library vendors (e.g. SGI) implement `auto_ptr_ref<Y>` with `Y*` as member which is much more reasonable. Other vendor implemented `auto_ptr_ref` as defectively required and it results with awkward and catastrophic code: `int oops = sink(auto_ptr<B>(source())); // warning recursive on all control paths`

Dave Abrahams noticed that there is no specification saying that `auto_ptr_ref` copy constructor can't throw.

My proposal comes to solve all the above issues and significantly simplify `auto_ptr` implementation. One of the fundamental requirements from `auto_ptr` is that it can be constructed in an intuitive manner (i.e. like ordinary pointers) but with strict ownership semantics which yield that source `auto_ptr` in initialization must be non-const. My idea is to add additional constructor template with sole propose to generate ill-formed, diagnostic required, instance for const auto_ptr arguments during instantiation of declaration. This special constructor will not be instantiated for other types which is achievable using 14.8.2/2 (SFINAE). Having this constructor in hand makes the constructor `template<class Y> auto_ptr(auto_ptr<Y> const&)` legitimate since the actual argument can't be const yet non const r-value are acceptable.

This implementation technique makes the "private auxiliary class" `auto_ptr_ref` obsolete and I found out that modern C++ compilers (e.g. EDG, GCC and VC) consume the new implementation as expected and allow all intuitive initialization and assignment cases while rejecting illegal cases that involve const `auto_ptr` arguments.

The proposed auto_ptr interface:

    namespace std {
        template<class X> class auto_ptr {
        public:
            typedef X element_type;

            // 20.4.5.1 construct/copy/destroy:
            explicit auto_ptr(X* p=0) throw();
            auto_ptr(auto_ptr&) throw();
            template<class Y> auto_ptr(auto_ptr<Y> const&) throw();
            auto_ptr& operator=(auto_ptr&) throw();
            template<class Y> auto_ptr& operator=(auto_ptr<Y>) throw();
            ~auto_ptr() throw();

            // 20.4.5.2 members:
            X& operator*() const throw();
            X* operator->() const throw();
            X* get() const throw();
            X* release() throw();
            void reset(X* p=0) throw();

        private:
            template<class U>
            auto_ptr(U& rhs, typename
    unspecified_error_on_const_auto_ptr<U>::type = 0);
        };
    }

One compliant technique to implement the `unspecified_error_on_const_auto_ptr` helper class is using additional private `auto_ptr` member class template like the following:

    template<typename T> struct unspecified_error_on_const_auto_ptr;

    template<typename T>
    struct unspecified_error_on_const_auto_ptr<auto_ptr<T> const>
    { typedef typename auto_ptr<T>::const_auto_ptr_is_not_allowed type; };

There are other techniques to implement this helper class that might work better for different compliers (i.e. better diagnostics) and therefore I suggest defining its semantic behavior without mandating any specific implementation. IMO, and I didn't found any compiler that thinks otherwise, 14.7.1/5 doesn't theoretically defeat the suggested technique but I suggest verifying this with core language experts.

Further changes in standard text:

Remove section 20.4.5.3

Change 20.4.5/2 to read something like: Initializing `auto_ptr<X>` from `const auto_ptr<Y>` will result with unspecified ill-formed declaration that will require unspecified diagnostic.

Change 20.4.5.1/4,5,6 to read:

    template<class Y> auto_ptr(auto_ptr<Y> const& a) throw();

4 Requires: `Y*` can be implicitly converted to `X*`.

5 Effects: Calls `const_cast<auto_ptr<Y>&>(a).release()`.

6 Postconditions: `*this` holds the pointer returned from `a.release()`.

Change 20.4.5.1/10

    template<class Y> auto_ptr& operator=(auto_ptr<Y> a) throw();

10 Requires: `Y*` can be implicitly converted to `X*`. The expression `delete get()` is well formed.

LWG TC DR #127 is obsolete.

Notice that the copy constructor and copy assignment operator should remain as before and accept non-`const auto_ptr&` since they have effect on the form of the implicitly declared copy constructor and copy assignment operator of class that contains auto_ptr as member per 12.8/5,10:

    struct X {
        // implicit X(X&)
        // implicit X& operator=(X&)
        auto_ptr<D> aptr_;
    };

In most cases this indicates about sloppy programming but preserves the current `auto_ptr` behavior.

Dave Abrahams encouraged me to suggest fallback implementation in case that my suggestion that involves removing of `auto_ptr_ref` will not be accepted. In this case removing the obsolete conversion member to `auto_ptr<Y>` and 20.4.5.3/4,5 is still required in order to eliminate ambiguity in legal cases. The two constructors that I suggested will co exist with the current members but will make `auto_ptr_ref` obsolete in initialization contexts. `auto_ptr_ref` will be effective in assignment contexts as suggested in DR #127 and I can't see any serious exception safety issues in those cases (although it's possible to synthesize such). `auto_ptr_ref<X>` semantics will have to be revised to say that it strictly holds pointer of type `X` and not reference to an `auto_ptr` for the favor of cases in which `auto_ptr_ref<Y>` is constructed from `auto_ptr<X>` in which `X` is different from `Y` (i.e. assignment from r-value derived to base).

[Redmond: punt for the moment. We haven't decided yet whether we want to fix auto_ptr for C++-0x, or remove it and replace it with move_ptr and unique_ptr.]

[ Oxford 2007: Recommend NAD. We're just going to deprecate it. It still works for simple use cases and people know how to deal with it. Going forward `unique_ptr` is the recommended tool. ]

[ 2007-11-09: Reopened at the request of David Abrahams, Alisdair Meredith and Gabriel Dos Reis. ]

[ 2009-07 Frankfurt ]

> This is a complicated issue, so we agreed to defer discussion until later in the week so that interested parties can read up on it.

[ 2009-10-04 Daniel adds: ]

> I suggest to close this issue as NAD. The reasons are two-fold: First, the suggested proposed resolution uses no longer appropriate language means to solve this issue, which has the effect that the recommended resolution is another - but better - form of hack. Second, either following the suggested resolution or the now more natural alternative via the added member set

> >     template<class Y> auto_ptr(auto_ptr<Y>&&) throw();
> >     template<class Y> auto_ptr& operator=(auto_ptr<Y>&&) throw();

> would still have a non-zero probability to break user-code that actively references `auto_ptr_ref`. This risk seems to indicate that a decision which would not touch the current spec of `auto_ptr` at all (but deprecating it) and instead recommending to use `unique_ptr` for new code instead might have the best cost-benefit ratio. IMO the current solution of 1100 can be considered as an active user-support for this transition.

[ 2009-10 Santa Cruz: ]

> Mark as NAD. Alisdair will open a new issue (1247) with proposed wording to handle `auto_ptr_ref`.

Proposed resolution:

Change the synopsis in 99 [auto.ptr]:

>     namespace std {
>       template <class Y> struct auto_ptr_ref {};

>       // exposition only
>       template <class T> struct constant_object;

>       // exposition only
>       template <class T>
>       struct cannot_transfer_ownership_from
>         : constant_object<T> {};

>       template <class X> class auto_ptr {
>       public:
>         typedef X element_type;

>         // D.9.1.1 construct/copy/destroy:
>         explicit auto_ptr(X* p =0) throw();
>         auto_ptr(auto_ptr&) throw();
>         template<class Y> auto_ptr(auto_ptr<Y> const&) throw();
>         auto_ptr& operator=(auto_ptr&) throw();
>         template<class Y> auto_ptr& operator=(auto_ptr<Y>&) throw();
>         auto_ptr& operator=(auto_ptr_ref<X> r) throw();
>         ~auto_ptr() throw();

>         // D.9.1.2 members:
>         X& operator*() const throw();
>         X* operator->() const throw();
>         X* get() const throw();
>         X* release() throw();
>         void reset(X* p =0) throw();

>         // D.9.1.3 conversions:
>         auto_ptr(auto_ptr_ref<X>) throw();
>         template<class Y> operator auto_ptr_ref<Y>() throw();
>         template<class Y> operator auto_ptr<Y>() throw();

>         // exposition only
>         template<class U>
>         auto_ptr(U& rhs, typename cannot_transfer_ownership_from<U>::error = 0);
>       };

>       template <> class auto_ptr<void>
>       {
>       public:
>         typedef void element_type;
>       };

>     }

Remove 99 [auto.ptr.conv].

Change 99 [auto.ptr], p3:

> The `auto_ptr` provides a semantics of strict ownership. An `auto_ptr` owns the object it holds a pointer to. Copying an `auto_ptr` copies the pointer and transfers ownership to the destination. If more than one `auto_ptr` owns the same object at the same time the behavior of the program is undefined. Templates `constant_object` and `cannot_transfer_ownership_from`, and the final constructor of `auto_ptr` are for exposition only. For any types `X` and `Y`, initializing `auto_ptr<X>` from `const auto_ptr<Y>` is ill-formed, diagnostic required. [Note: The uses of `auto_ptr` include providing temporary exception-safety for dynamically allocated memory, passing ownership of dynamically allocated memory to a function, and returning dynamically allocated memory from a function. `auto_ptr` does not meet the `CopyConstructible` and `Assignable` requirements for Standard Library container elements and thus instantiating a Standard Library container with an `auto_ptr` results in undefined behavior. -- end note]

Change [auto.ptr.cons], p5:

>     template<class Y> auto_ptr(auto_ptr<Y> const& a) throw();

> > Requires: `Y*` can be implicitly converted to `X*`.

> > Effects: Calls `const_cast<auto_ptr<Y>&>(``a``)``.release()`.

> > Postconditions: `*this` holds the pointer returned from `a.release()`.

Change [auto.ptr.cons], p10:

>     template<class Y> auto_ptr& operator=(auto_ptr<Y>& a) throw();

> > Requires: `Y*` can be implicitly converted to `X*`. The expression `delete get()` is well formed.

> > Effects: Calls `reset(a.release())`.

> > Returns: `*this`.