ISO/ IEC JTC1/SC22/WG21 N4190

Document number: N4190
Date: 2014-10-09
Project: Programming Language C++, Library Evolution Working Group
Reply-to: Stephan T. Lavavej <[email protected]>


Removing auto_ptr, random_shuffle(), And Old <functional> Stuff


I. Introduction

The C++ Standard is big.  We should make it smaller by removing stuff we
don't need anymore, like features that have been deprecated for years and
thoroughly superseded by more modern machinery.


II. What Must Live

D.1 "Increment operator with bool operand" [depr.incr.bool]
D.2 "register keyword" [depr.register]
D.3 "Implicit declaration of copy functions" [depr.impldec]
D.4 "Dynamic exception specifications" [depr.except.spec]
These Core Language features aren't within the LEWG/LWG's jurisdiction.

D.5 "C standard library headers" [depr.c.headers]
Why is this even deprecated?

D.6 "Old iostreams members" [depr.ios.members]
This was deprecated when C++98 shipped, but I don't know how widely this stuff
is being used in 2014, or how difficult it would be to change affected code.

D.7 "char* streams" [depr.str.strstreams]
This was also deprecated when C++98 shipped.  The LWG felt that strstreams
haven't been superseded with respect to performance, so they can't be removed
at this time.

D.11 "Violating exception-specifications" [exception.unexpected]
These are Library helpers for D.4 [depr.except.spec]'s Core tech.


III. What Must Die


D.8 "Function objects" [depr.function.objects]

This defines unary_function/binary_function, ptr_fun(),
and mem_fun()/mem_fun_ref().

unary_function/binary_function were useful helpers when C++98-era adaptors
needed argument_type/etc. typedefs.  Such typedefs are unnecessary given
C++11's perfect forwarding, decltype, and so forth.  (And they're inapplicable
to overloaded/templated function call operators.)  Even if a class wants to
provide these typedefs for backwards compatibility, it can do so directly
(at a minor cost in verbosity) instead of inheriting from
unary_function/binary_function, which is what the Standard itself started
doing when these helpers were deprecated.

ptr_fun() was necessary when function pointers had to be wrapped in classes
before being given to C++98-era adaptors.  Again, this is unnecessary in the
C++11 world, when function pointers can be given directly to bind(),
std::function, and so forth.  The only C++98-era adaptors lurking in
non-deprecated C++14 are not1()/not2() in 20.9.8 [negators], but
(1) lambdas can conveniently negate function calls, and (2) the old negators
are being superseded by Library Fundamentals v2's not_fn()
in 2.2 [func.not_fn].

mem_fun()/mem_fun_ref() were strictly superseded by mem_fn().


D.9 "Binders" [depr.lib.binders]

This defines bind1st()/bind2nd(), which were strictly superseded by bind().
(In the future, I'll argue that bind() itself has been superseded by lambdas
and especially generic lambdas, so bind() should be deprecated, but that
isn't part of this proposal.)


D.10 "auto_ptr" [depr.auto.ptr]

auto_ptr has been superseded by unique_ptr.  Any code using auto_ptr can be
mechanically converted to using unique_ptr, with move() inserted whenever
auto_ptr was being "copied".  clang-modernize's Replace-AutoPtr Transform
does exactly this (see [1]).


D.12 "Random shuffle" [depr.alg.random.shuffle]

This defines random_shuffle(first, last) and random_shuffle(first, last, rng).
(The latter takes a RandomNumberGenerator, whose requirements are
totally different from C++11's UniformRandomNumberGenerator.)

The problem with random_shuffle(first, last) is that it's permitted to use
rand(), which is permitted to be low quality.  (rand() is even permitted to
introduce data races, 26.8 [c.math]/5, although I'm not aware of any
implementation that does so.)  Constructing mt19937 urng and calling
shuffle(first, last, urng) is a far superior alternative.  Unlike
random_shuffle(first, last), calling shuffle(first, last, urng) requires the
user to be aware of the URNG's existence and state, but I argue that
this is a feature, not a bug.

random_shuffle(first, last, rng) is the Knuth shuffle algorithm.
It's not evil, but it's almost unusable in practice, because the "rng"
function object is required to provide a very strange interface.
(It's actually required to return a uniform integer distribution over a
varying interval, which is difficult to do without introducing bias.)
shuffle(first, last, urng) is vastly easier to use, because it directly
consumes a URNG.


IV. Questions And Answers


Q1. Is this stuff really icky and bad and yucky?

A1. Yes.  Remember that we've already made the difficult decisions to deprecate
this stuff.  Now we're faced with the easier task of simply verifying that each
deprecated component really has been superseded by future technology.


Q2. Will removing this stuff from the Standard immediately break the world?

A2. No.  Remember that the Standard is not an implementation.
An implementation is free to provide a strictly conforming C++14 mode with all
of this deprecated stuff, and a strictly conforming C++17 mode without any of
this stuff.  Also, an implementation can provide a C++17 mode with the very
slightly nonconformant behavior of also implementing this stuff according to
C++14's specification.  It would be nonconforming in the sense that if C++17
doesn't mention auto_ptr, then "using namespace std;" shouldn't drag in such
a name, and users should be free to macroize auto_ptr.  In practice, we all
know that such a hybrid mode wouldn't be problematic.  Practically speaking,
implementations will be able to migrate away from deprecated-then-removed
stuff as quickly or as slowly as they like.

Because none of this machinery depends on "magic" (unlike unexpected(),
for example), a third party can provide it even if a Standard Library vendor
chooses not to.  (The only exception is auto_ptr's integration with
unique_ptr/shared_ptr.)


Q3. Then why bother removing anything?

A3. Removing this stuff from the Standard will reduce the editors and LWG's
burdens - slightly, but nonzero.

More significantly, it sends a very clear message to authors, teachers,
and the C++ community that this stuff shouldn't be used anymore.

Finally, it allows implementers to discard their implementations,
if they wish to be aggressive about migration.


V. Standardese

1. In 20.7.2 "Header <memory> synopsis" [memory.syn], remove:

// D.10, auto_ptr (deprecated)
template <class X> class auto_ptr;

2. In 20.8.1.2 [unique.ptr.single], remove:

template <class U>
  unique_ptr(auto_ptr<U>&& u) noexcept;

3. Remove 20.8.1.2.1 [unique.ptr.single.ctor]/22-24:

template <class U>
  unique_ptr(auto_ptr<U>&& u) noexcept;
Effects: Constructs a unique_ptr object, initializing the stored pointer with
u.release() and value-initializing the stored deleter.
Postconditions: get() yields the value u.get() yielded before the construction.
u.get() == nullptr. get_deleter() returns a reference to the stored deleter.
Remarks: This constructor shall not participate in overload resolution unless
U* is implicitly convertible to T* and D is the same type as default_delete<T>.

4. In 20.8.2.2 [util.smartptr.shared], remove:

template<class Y> shared_ptr(auto_ptr<Y>&& r);

5. In 20.8.2.2 [util.smartptr.shared], remove:

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

6. Remove 20.8.2.2.1 [util.smartptr.shared.const]/28-32:

template<class Y> shared_ptr(auto_ptr<Y>&& r);
Requires: r.release() shall be convertible to T*. Y shall be a complete type.
The expression delete r.release() shall be well formed, shall have well defined
behavior, and shall not throw exceptions.
Effects: Constructs a shared_ptr object that stores and owns r.release().
Postconditions: use_count() == 1 && r.get() == 0.
Throws: bad_alloc, or an implementation-defined exception when a resource other
than memory could not be obtained.
Exception safety: If an exception is thrown, the constructor has no effect.

7. In 20.8.2.2.3 [util.smartptr.shared.assign], remove:

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

8. In 20.9 [function.objects]/2 "Header <functional> synopsis", remove:

// D.8.1, base (deprecated):
template <class Arg, class Result> struct unary_function;
template <class Arg1, class Arg2, class Result> struct binary_function;

9. In 20.9 [function.objects]/2 "Header <functional> synopsis", remove:

// D.9, binders (deprecated):
template <class Fn> class binder1st;
template <class Fn, class T>
  binder1st<Fn> bind1st(const Fn&, const T&);
template <class Fn> class binder2nd;
template <class Fn, class T>
  binder2nd<Fn> bind2nd(const Fn&, const T&);

// D.8.2.1, adaptors (deprecated):
template <class Arg, class Result> class pointer_to_unary_function;
template <class Arg, class Result>
  pointer_to_unary_function<Arg,Result> ptr_fun(Result (*)(Arg));
template <class Arg1, class Arg2, class Result>
  class pointer_to_binary_function;
template <class Arg1, class Arg2, class Result>
  pointer_to_binary_function<Arg1,Arg2,Result>
    ptr_fun(Result (*)(Arg1,Arg2));

// D.8.2.2, adaptors (deprecated):
template<class S, class T> class mem_fun_t;
template<class S, class T, class A> class mem_fun1_t;
template<class S, class T>
    mem_fun_t<S,T> mem_fun(S (T::*f)());
template<class S, class T, class A>
    mem_fun1_t<S,T,A> mem_fun(S (T::*f)(A));
template<class S, class T> class mem_fun_ref_t;
template<class S, class T, class A> class mem_fun1_ref_t;
template<class S, class T>
    mem_fun_ref_t<S,T> mem_fun_ref(S (T::*f)());
template<class S, class T, class A>
    mem_fun1_ref_t<S,T,A> mem_fun_ref(S (T::*f)(A));

template <class S, class T> class const_mem_fun_t;
template <class S, class T, class A> class const_mem_fun1_t;
template <class S, class T>
  const_mem_fun_t<S,T> mem_fun(S (T::*f)() const);
template <class S, class T, class A>
  const_mem_fun1_t<S,T,A> mem_fun(S (T::*f)(A) const);
template <class S, class T> class const_mem_fun_ref_t;
template <class S, class T, class A> class const_mem_fun1_ref_t;
template <class S, class T>
  const_mem_fun_ref_t<S,T> mem_fun_ref(S (T::*f)() const);
template <class S, class T, class A>
  const_mem_fun1_ref_t<S,T,A> mem_fun_ref(S (T::*f)(A) const);

10. In 25.1 [algorithms.general] "Header <algorithm> synopsis", remove:

// D.12, random_shuffle (deprecated):
template<class RandomAccessIterator>
  void random_shuffle(RandomAccessIterator first,
                      RandomAccessIterator last);
template<class RandomAccessIterator, class RandomNumberGenerator>
  void random_shuffle(RandomAccessIterator first,
                      RandomAccessIterator last,
                      RandomNumberGenerator&& rng);

11. After C.3 [diff.cpp11], add a new section:

C++ and ISO C++ 2014 [diff.cpp14]
This subclause lists the differences between C++ and ISO C++ 2014 (ISO/IEC
14882:2014, Programming Languages - C++), by the chapters of this document.

12. As a child of [diff.cpp14], add a new section:

Annex D: compatibility features [diff.cpp14.depr]
Change: The class templates auto_ptr, unary_function, and binary_function, the
function templates random_shuffle, and the function templates (and their return
types) ptr_fun, mem_fun, mem_fun_ref, bind1st, and bind2nd are not defined.
Rationale: Superseded by new features.
Effect on original feature: Valid C++ 2014 code that uses these class templates
and function templates may fail to compile in this International Standard.

13. Remove the section D.8 [depr.function.objects].

14. Remove the section D.9 [depr.lib.binders].

15. Remove the section D.10 [depr.auto.ptr].

16. Remove the section D.12 [depr.alg.random.shuffle].


VI. Acknowledgements

Thanks to Billy O'Neal and Giovanni Dicanio for reviewing this proposal.


VII. References

All of the Standardese citations in this proposal are to Working Paper N3936:
http://www.open-std.org/jtc1/sc22/wg21/prot/14882fdis/n3936.pdf

All of the Library Fundamentals v2 citations in this proposal are to
Working Draft N4084:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4084.html

[1] clang-modernize documentation, Replace-AutoPtr Transform:
http://clang.llvm.org/extra/ReplaceAutoPtrTransform.html

(end)