ISO/ IEC JTC1/SC22/WG21 N3588

Document number: N3588
Date: 2013-03-15
Project: Programming Language C++, Library Evolution Working Group
Reply-to: Stephan T. Lavavej <stl@microsoft.com>


make_unique


I. Introduction

This is a proposal to add make_unique for symmetry, simplicity, and safety.


II. Motivation And Scope

C++11 provided make_shared for shared_ptr, but not make_unique for unique_ptr.
This is widely viewed as an oversight. While it isn't absolutely necessary for
the Standard to provide make_unique (because skilled users can implement it
with perfect efficiency), it's still important. The implementation of
make_unique<T>(args...), a perfectly forwarding variadic template, is difficult
for beginners to understand. make_unique<T[]> involves even more subtleties.
Because make_unique is commonly desired, adding it to the Standard Library will
put an end to the proliferation of user implementations.

make_unique's presence in the Standard Library will have several wonderful
consequences. It will be possible to teach users "never say new/delete
/new[]/delete[]" without disclaimers. Additionally, make_unique shares two
advantages with make_shared (excluding the third advantage, increased
efficiency). First, unique_ptr<LongTypeName> up(new LongTypeName(args)) must
mention LongTypeName twice, while auto up = make_unique<LongTypeName>(args)
mentions it once. Second, make_unique prevents the unspecified-evaluation-order
leak triggered by expressions like foo(unique_ptr<X>(new X),
unique_ptr<Y>(new Y)). (Following the advice "never say new" is simpler than
"never say new, unless you immediately give it to a named unique_ptr".)


III. Impact On The Standard

This proposal has no dependencies beyond C++11.

Nothing depends on this proposal.

This proposal is a pure extension to <memory>. It doesn't affect user code at
all, except for adding new names to namespace std.


IV. Design Decisions

Let's examine unique_ptr<T, D>'s possible arguments.

1. T, "for single objects"
    This is the easiest case, although there are some issues to consider:

    make_unique<T>(args...) - invokes new T(std::forward<Args>(args)...)

    First issue: Should this say T(stuff) with parentheses or T{stuff} with
    braces?
        This says T(stuff) with parentheses for symmetry with make_shared<T>().
        It would be very strange if make_unique<vector<int>>(1998, 2011)
        constructed a vector with size() == 2, while make_shared<vector<int>>(
        1998, 2011) constructs a vector with size() == 1998. Additionally, the
        function call make_unique<T>(stuff) uses parentheses, so constructing
        T(stuff) with parentheses is unsurprising.

    Second issue: Should this say plain new or global ::new?
        This says plain new because default_delete invokes plain delete
        (20.7.1.1.2 [unique.ptr.dltr.dflt]/3). Note that make_shared<T>() says
        ::new (pv) because it must invoke "true placement new" (which can't be
        replaced, 18.6.1.3 [new.delete.placement]/1) instead of
        T::operator new(size_t, void *) (which is allowed to exist).

    Third issue: Given zero arguments, should this say new T() for
    value-initialization or new T for default-initialization?
        This says new T() for value-initialization for several reasons:
        symmetry with make_shared<T>(), consistency with the empty parentheses
        in the function call make_unique<T>(), predictability as this is the
        natural result of a variadic implementation, and most importantly
        safety ("zero is a strict subset of garbage"). The only reason for a
        user to want default-initialization would be performance.
        (make_unique<T>() is faster than make_shared<T>(), so performance
        concerns can't be immediately dismissed by appealing to symmetry.)
        However, it is extremely likely that the cost of a dynamic memory
        allocation (plus the corresponding deallocation) will dwarf the cost of
        value-initializing a single object, even if it's a large POD struct.
        This proposal actually provides default-initialization for single
        objects with special syntax; see below.

2. T[], "for array objects with a runtime length"
    Let's consider all possible new-expressions for the Standard Library to
    imitate. Curiously, 5.3.4 [expr.new]/1 forbids new int[]{ 11, 22, 33 }
    because int[] is incomplete. (This may change when Core Issue 1469 is
    resolved.) Let's pretend that this is well-formed.

    The optional new-initializer could be omitted, (), (expression-list), or
    braced-init-list. 8.5 [dcl.init]/17 says that (expression-list) is
    ill-formed when the destination type is an array (there's a special case
    for string literals, but the Standard Library can't detect them). That
    leaves three new-initializers, resulting in six cases:

    new T[] - ill-formed, no length information
    new T[]() - ill-formed, no length information
    new T[]{ up, down, strange } - (should be) well-formed, length deduced

    new T[n] - default-initialized
    new T[n]() - value-initialized
    new T[n]{ charm, bottom, top } - value-initialized if
    initializer-clauses < n, bad_array_new_length if initializer-clauses > n

    Note that 5.3.4 [expr.new]/7 permits new T[0], but 8.5.1 [dcl.init.aggr]/4
    forbids T t[]{}. Also note that default-initialization versus
    value-initialization is increasingly important for increasingly large
    arrays.

    Because the Core Language provides multiple ways to initialize dynamically
    allocated arrays, users will expect the Standard Library to provide the
    same things. To avoid confusion, the following syntax is proposed:

    make_unique<T[]>(n) - invokes new T[n](), requesting value-initialization.
    (This can be thought of as moving the length out of the parentheses and
    into the square brackets.) This is safe (again, "zero is a strict subset of
    garbage"), consistent with vector/etc., and consistent with
    make_unique/make_shared for single objects as mentioned above. Because this
    form will be used frequently, it is deliberately being kept very
    simple - in particular, elements cannot be specified.

    make_unique_default_init<T[]>(n) - invokes new T[n], requesting
    default-initialization. This requires users to explicitly say that they
    want higher performance at the cost of getting garbage bits, but doesn't
    otherwise burden them. Note that elements cannot be specified, because the
    Core Language doesn't allow that mix.

    make_unique_value_init<T[]>(n, args...) - invokes
    new T[n]{ std::forward<Args>(args)... }, requesting value-initialization
    for extra elements at the end. This is mostly being provided for
    completeness - it's not too much trouble to specify, and it may be useful
    in a few situations. Note that providing zero elements makes this
    equivalent to the ordinary form; banning this case isn't necessary.

    make_unique_auto_size<T[]>(args...) - invokes
    new T[sizeof...(Args)]{ std::forward<Args>(args)... }, for convenience and
    consistency with the long-standing ability to say
    int arr[] = { 11, 22, 33 }. ("auto_size" is proposed for terseness; other
    alternatives are "auto_length", "deduce_size", "deduce_length", etc.)

    This syntax can be extended to single objects, satisfying users who really
    want garbage bits:

    make_unique_default_init<T>() - invokes new T, requesting
    default-initialization. (To avoid confusion, args cannot be specified.)

3. T[N]
    As of N3485, unique_ptr doesn't provide a partial specialization for T[N].
    However, users will be strongly tempted to write make_unique<T[N]>(). This
    is a no-win scenario. Returning unique_ptr<T[N]> would select the primary
    template for single objects, which is bizarre. Returning unique_ptr<T[]>
    would be an exception to the otherwise ironclad rule that
    make_unique<something>() returns unique_ptr<something>. Therefore, this
    proposal makes T[N] ill-formed here, allowing implementations to emit
    helpful static_assert messages.

4. Custom deleters
    While C++11 provides make_shared and allocate_shared, this proposal
    provides make_unique without allocate_unique. Here are the reasons for this
    asymmetry. First and most importantly, make_shared and allocate_shared can
    do things that users can't do. (Specifically, they can implement the
    "we know where you live" optimization with special control blocks.) This
    doesn't apply to unique_ptr. Expert users with custom allocators can easily
    write their own wrappers with corresponding custom deleters. In contrast,
    make_unique will be used by programmers of all skill levels, and beginners
    cannot be expected to write perfectly forwarding variadic templates.
    Intermediate/advanced programmers can, but the need for make_unique is so
    common (and arrays are surprisingly subtle) that it makes sense to
    standardize.

    Second, specifying allocate_unique would increase complexity, multiplied by
    single objects versus arrays. This is unlike allocate_shared, which is a
    single additional overload (because shared_ptr doesn't deal with arrays).

    Third, allocate_unique's complexity would result from the fact that the
    Standard Library currently doesn't contain enough machinery to implement
    it - specifically, to adapt allocator syntax to deleter syntax. (Returning
    unique_ptr<T, unspecified> would be inconvenient for users.) This is unlike
    allocate_shared, because shared_ptr is powered by type erasure.

    While this proposal doesn't provide allocate_unique (and is recommending
    that it never be provided), one way to provide it without introducing a
    wrapper class could be to unify deleters and allocators in unique_ptr's
    specification. That is, specifying that if the expression d(ptr) is not
    valid, then D shall meet the Allocator requirements.

Syntax summary:
                 make_unique<T>(args...)
    make_unique_default_init<T>()

                 make_unique<T[]>(n)
    make_unique_default_init<T[]>(n)
      make_unique_value_init<T[]>(n, args...)
       make_unique_auto_size<T[]>(args...)

Questions and answers:

Q1. Can initializer_list<T> be used to simplify the syntax?

A1. Unfortunately, no. Consider new X[n]{ a, b, c } when X is
default constructible and constructible from A/B/C, but non-copyable and
non-movable.

Q2. Can users make their constructors non-public, then grant friendship to
make_unique?

A2. This is tracked by Library Issue 2070 for make_shared/allocate_shared. For
make_unique, two things could interfere with friendship: having a helper
function invoke the new-expression, or modifying the user-facing function's
signature. Unfortunately, switching between the highly desirable syntax
make_unique<T> versus make_unique<T[]> involves one or the other: tag dispatch
to helper functions, or SFINAE in user-facing signatures (used by the
implementation below). The Standard typically doesn't limit implementer freedom
to use helper functions, but it also doesn't depict SFINAE in signatures.

Q3. Can make_unique<T[]>(n) and make_unique_value_init<T[]>(n, args...)
be unified?

A3. Yes, at the cost of endless confusion. Quick, how many ints does
make_unique<int[]>(11, 22, 33, 44) allocate, 4 or 11? So, no.

Q4. Can make_unique_value_init<T[]>(n, args...) and
make_unique_auto_size<T[]>(args...) be eliminated?

A4. Yes! The remaining syntax would be simple and symmetric:

             make_unique<T>(args...)
make_unique_default_init<T>()
             make_unique<T[]>(n)
make_unique_default_init<T[]>(n)

It wouldn't expose the full power of new-expressions in the C++11 Core
Language, but advanced users can write their own wrappers and choose their own
syntax, while the 4 signatures above would suffice for the vast majority of
users.


V. Standardese

To be provided after consensus on the interface has been reached.


VI. Acknowledgements

Thanks to Hyman Rosen via Dilip Ranganathan for various suggestions that led me
to think deeply about the array cases.

Thanks to Bill Plauger, Josh Rowe, Marshall Clow, Phil Deets, and Vinny Romano
for reviewing this proposal.


VII. References

All of the citations in this proposal are to Working Paper N3485:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3485.pdf

Core Issue 1469, "Omitted bound in array new-expression":
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1469

Library Issue 2070, "allocate_shared should use allocator_traits<A>::construct":
http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2070


VIII. Implementation

#include <cstddef>
#include <memory>
#include <type_traits>
#include <utility>

namespace std {
    template<class T> struct _Never_true : false_type { };

    template<class T> struct _Unique_if {
        typedef unique_ptr<T> _Single;
    };

    template<class T> struct _Unique_if<T[]> {
        typedef unique_ptr<T[]> _Runtime;
    };

    template<class T, size_t N> struct _Unique_if<T[N]> {
        static_assert(_Never_true<T>::value, "make_unique forbids T[N]. Please use T[].");
    };

    template<class T, class... Args> typename _Unique_if<T>::_Single make_unique(Args&&... args) {
        return unique_ptr<T>(new T(std::forward<Args>(args)...));
    }

    template<class T> typename _Unique_if<T>::_Single make_unique_default_init() {
        return unique_ptr<T>(new T);
    }

    template<class T> typename _Unique_if<T>::_Runtime make_unique(size_t n) {
        typedef typename remove_extent<T>::type U;
        return unique_ptr<T>(new U[n]());
    }

    template<class T> typename _Unique_if<T>::_Runtime make_unique_default_init(size_t n) {
        typedef typename remove_extent<T>::type U;
        return unique_ptr<T>(new U[n]);
    }

    template<class T, class... Args> typename _Unique_if<T>::_Runtime make_unique_value_init(size_t n, Args&&... args) {
        typedef typename remove_extent<T>::type U;
        return unique_ptr<T>(new U[n]{ std::forward<Args>(args)... });
    }

    template<class T, class... Args> typename _Unique_if<T>::_Runtime make_unique_auto_size(Args&&... args) {
        typedef typename remove_extent<T>::type U;
        return unique_ptr<T>(new U[sizeof...(Args)]{ std::forward<Args>(args)... });
    }
}

#include <iostream>
#include <string>
using namespace std;

void print(const unique_ptr<int[]>& up) {
    for (int i = 0; i < 5; ++i) {
        cout << up[i] << " ";
    }

    cout << endl;
}

int main() {
    cout << *make_unique<int>() << endl;
    cout << *make_unique<int>(1729) << endl;
    cout << "\"" << *make_unique<string>() << "\"" << endl;
    cout << "\"" << *make_unique<string>("meow") << "\"" << endl;
    cout << "\"" << *make_unique<string>(6, 'z') << "\"" << endl;

    cout << *make_unique_default_init<int>() << endl;
    cout << "\"" << *make_unique_default_init<string>() << "\"" << endl;

    print(make_unique<int[]>(5));
    print(make_unique_default_init<int[]>(5));
    print(make_unique_value_init<int[]>(5, 100, 200, 300));
    print(make_unique_auto_size<int[]>(111, 222, 333, 444, 555));
}

Output, when -842150451 is the result of an 0xCDCDCDCD debug fill pattern:

0
1729
""
"meow"
"zzzzzz"
-842150451
""
0 0 0 0 0
-842150451 -842150451 -842150451 -842150451 -842150451
100 200 300 0 0
111 222 333 444 555

(end)